You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用django-oauth-toolkit交换令牌时遇invalid_grant错误求助

Django OAuth Toolkit 持续出现invalid_grant错误排查

问题概述

使用django-oauth-toolkit实现OAuth2授权码流程时,始终返回invalid_grant错误。已确认redirect URL、client id、client secret与Django后台注册信息一致,且code verifier和code challenge的生成逻辑符合要求,但问题仍未解决。

测试脚本

import random
import string
import base64
import hashlib

code_verifier = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(random.randint(43, 128)))
code_verifier = base64.urlsafe_b64encode(code_verifier.encode('utf-8'))

code_challenge = hashlib.sha256(code_verifier).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8').replace('=', '')

client_id = input("Enter client id: ")
client_secret = input("Enter client secret: ")
redirect_url = input("Enter callback url: ")

print(f'vist http://127.0.0.1:8000/o/authorize/?response_type=code&code_challenge={code_challenge}&code_challenge_method=S256&client_id={client_id}&redirect_uri={redirect_url}')

code = input("Enter code: ")

print(f'enter: curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "client_id={client_id}" -d "client_secret={client_secret}" -d "code={code}" -d "code_verifier={code_verifier}" -d "redirect_uri={redirect_url}" -d "grant_type=authorization_code"')

运行输出

(venv) (base) peter@Peters-MacBook-Air Intra-Site % python api_test.py 
Enter client id: sJ3ijzbmdogfBZPfhkF6hOqifPuPSKKpOnN8hq1N
Enter client secret: GnXNWqw7t1OwPRbOWTmDXKiuaqeJ7LRMhY9g2CWe00f3QrHLvx6aDKjGf5eF1t6QPkD1YO8BR43HNmzCjZYBW81FIjTng7QnVypzshMljEJRGTj5N7r8giwjKIiXyVng
Enter callback url: https://192.168.1.44/authenticate/callback
vist http://127.0.0.1:8000/o/authorize/?response_type=code&code_challenge=WiaJdysQ6aFnmkaO8yztt9kPBGUj-aqZSFVgmWYRBlU&code_challenge_method=S256&client_id=sJ3ijzbmdogfBZPfhkF6hOqifPuPSKKpOnN8hq1N&redirect_uri=https://192.168.1.44/authenticate/callback
Enter code: hrLeADVEmQF8mDLKJXhAZJRQ2dElV7
enter: curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "client_id=sJ3ijzbmdogfBZPfhkF6hOqifPuPSKKpOnN8hq1N" -d "client_secret=GnXNWqw7t1OwPRbOWTmDXKiuaqeJ7LRMhY9g2CWe00f3QrHLvx6aDKjGf5eF1t6QPkD1YO8BR43HNmzCjZYBW81FIjTng7QnVypzshMljEJRGTj5N7r8giwjKIiXyVng" -d "code=hrLeADVEmQF8mDLKJXhAZJRQ2dElV7" -d "code_verifier=b'UDZGREwyR0ZVMjNCTzRBQlFaVlBUQk9TWkVRUDlCQzFSUTY1MkFNMUUzRTVCRVBNNlkwR0k4UEtGMUhaVVlTVkQ0QVowMzJUR0xLTDQ1U0VNOEtaWVcxT0tP'" -d "redirect_uri=https://192.168.1.44/authenticate/callback" -d "grant_type=authorization_code"
(venv) (base) peter@Peters-MacBook-Air Intra-Site % curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "client_id=sJ3ijzbmdogfBZPfhkF6hOqifPuPSKKpOnN8hq1N" -d "client_secret=GnXNWqw7t1OwPRbOWTmDXKiuaqeJ7LRMhY9g2CWe00f3QrHLvx6aDKjGf5eF1t6QPkD1YO8BR43HNmzCjZYBW81FIjTng7QnVypzshMljEJRGTj5N7r8giwjKIiXyVng" -d "code=hrLeADVEmQF8mDLKJXhAZJRQ2dElV7" -d "code_verifier=b'UDZGREwyR0ZVMjNCTzRBQlFaVlBUQk9TWkVRUDlCQzFSUTY1MkFNMUUzRTVCRVBNNlkwR0k4UEtGMUhaVVlTVkQ0QVowMzJUR0xLTDQ1U0VNOEtaWVcxT0tP'" -d "redirect_uri=https://192.168.1.44/authenticate/callback" -d "grant_type=authorization_code"
{"error": "invalid_grant"}%

配置信息

settings.py

OAUTH2_PROVIDER = {
    'ALLOWED_REDIRECT_URI_SCHEMES': ["https", "intra"],
    "SCOPES": {
        "read": "Read scope",
        "write": "Write scope",
        "groups": "Access to your groups",
    },
}

REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "oauth2_provider.contrib.rest_framework.OAuth2Authentication",
    ],
}

已安装应用

"oauth2_provider",
"rest_framework",

错误原因与解决方法

核心问题:code_verifier格式错误

从运行输出的curl命令可以看到,code_verifier参数值带有b''前缀,这是因为脚本中code_verifier被处理为bytes对象,直接输出时保留了Python的bytes类型标识。服务器收到这个带前缀的字符串后,无法与生成code_challenge时使用的原始bytes值匹配,导致PKCE验证失败,返回invalid_grant。

修正脚本

修改脚本中code_verifier的生成逻辑,将bytes对象解码为字符串,并移除base64编码后的多余等号(符合PKCE规范):

import random
import string
import base64
import hashlib

code_verifier = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(random.randint(43, 128)))
# 修正:将bytes解码为字符串,并移除末尾的等号
code_verifier = base64.urlsafe_b64encode(code_verifier.encode('utf-8')).decode('utf-8').replace('=', '')

# 注意:计算code_challenge时,要使用code_verifier的字节形式
code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8').replace('=', '')

client_id = input("Enter client id: ")
client_secret = input("Enter client secret: ")
redirect_url = input("Enter callback url: ")

print(f'vist http://127.0.0.1:8000/o/authorize/?response_type=code&code_challenge={code_challenge}&code_challenge_method=S256&client_id={client_id}&redirect_uri={redirect_url}')

code = input("Enter code: ")

print(f'enter: curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/token/" -d "client_id={client_id}" -d "client_secret={client_secret}" -d "code={code}" -d "code_verifier={code_verifier}" -d "redirect_uri={redirect_url}" -d "grant_type=authorization_code"')

额外检查点

  1. 授权码唯一性:authorization code是一次性使用的,若重复提交同一code会直接返回invalid_grant,确保每次请求使用新生成的code。
  2. redirect_uri完全匹配:请求token时的redirect_uri必须与授权请求时的完全一致,包括大小写、是否带末尾斜杠等细节。
  3. 应用类型匹配:若Django后台注册的应用是Public类型,请求token时无需传递client_secret(传递会导致验证失败);若为Confidential类型则正常传递。
  4. 时区与过期时间:确认服务器与本地时区一致,避免授权码因时间差提前过期(默认过期时间为5分钟)。

内容的提问来源于stack exchange,提问作者Peter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 23:13:12