使用requests-oauthlib获取API令牌失败,手动请求正常
问题背景
使用requests-oauthlib实现OAuth2授权码流程时,跳转至授权页后回调触发oauthlib.oauth2.rfc6749.errors.AccessDeniedError: (access_denied)错误,但手动构造HTTP请求实现相同逻辑可成功获取令牌。
requests-oauthlib实现代码(报错版本)
from flask import Flask, request, redirect from requests_oauthlib import OAuth2Session # 配置常量(省略) app = Flask(__name__) app.config["SECRET_KEY"] = SECRET_KEY @app.route("/login/") def login(): api_session = OAuth2Session(API_CLIENT_ID, redirect_uri=API_REDIRECT_URI) authorization_url, state = api_session.authorization_url(API_AUTHORIZATION_BASE_URL) session["oauth_state"] = state return redirect(authorization_url) @app.route("/callback/") def callback(): api_session = OAuth2Session(API_CLIENT_ID, state=session["oauth_state"]) token = api_session.fetch_token( API_TOKEN_URL, client_secret=API_CLIENT_SECRET, authorization_response=request.url, include_client_id=True, code=request.args.get("code"), ) return "Tokens received."
手动请求实现代码(成功版本)
import requests from flask import Flask, request, redirect # 配置常量(省略) app = Flask(__name__) app.config["SECRET_KEY"] = SECRET_KEY @app.route("/login/") def login(): return redirect( f"{API_AUTHORIZATION_BASE_URL}?client_id={API_CLIENT_ID}&redirect_uri={API_REDIRECT_URI}&response_type=code" ) @app.route("/callback/") def callback(): data = { "client_secret": API_CLIENT_SECRET, "grant_type": "authorization_code", "code": request.args.get("code"), "client_id": API_CLIENT_ID, "redirect_uri": API_REDIRECT_URI, } response = requests.post(API_TOKEN_URL, data=data) return "Tokens received."
排查方向
对比授权请求URL参数差异
requests-oauthlib的authorization_url()默认会自动添加state参数,若初始化时未指定scope还会附加空值的scope=参数。部分不符合OAuth2规范的第三方API会拒绝包含未约定参数的请求。
操作:打印authorization_url的值,和手动构造的URL对比,若存在额外参数,可通过以下方式移除:# 初始化时禁用state api_session = OAuth2Session(API_CLIENT_ID, redirect_uri=API_REDIRECT_URI, state=None) # 或者指定空scope(如果API不需要scope) authorization_url, state = api_session.authorization_url(API_AUTHORIZATION_BASE_URL, scope='')修正fetch_token参数传递
代码中同时传入了authorization_response=request.url和code=request.args.get("code"),前者会自动从回调URL中提取code,重复传递会导致参数冲突。
操作:移除code=request.args.get("code")参数,仅保留authorization_response=request.url。检查令牌请求的编码与头信息
requests-oauthlib的fetch_token请求可能与手动请求的Content-Type、编码方式存在差异。
操作:开启requests-oauthlib的debug日志,或用抓包工具对比两次令牌请求的参数、请求头、编码格式,确保与手动请求一致。验证redirect_uri的编码一致性
requests-oauthlib会自动对redirect_uri进行URL编码,若第三方API仅识别未编码或特定编码格式的URI,会导致授权失败。
操作:对比authorization_url中的redirect_uri部分与手动构造的URI,确保编码格式一致。
内容的提问来源于stack exchange,提问作者Ralf Zosel

