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

Django Admin创建Gmail草稿遇Google OAuth重定向不匹配错误

Django Admin Gmail草稿创建:OAuth redirect_uri_mismatch问题

问题描述

尝试实现Django Admin动作,为选中的联系人在Gmail账户中创建邮件草稿,但在Google OAuth流程中触发redirect_uri_mismatch错误,报错发生在flow.fetch_token(code=code)代码行。


相关代码

admin.py 代码

...

DEBUG = os.getenv('DEBUG', 'False') == 'True'
if DEBUG:
    os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

SCOPES = ['https://www.googleapis.com/auth/gmail.compose']

def email_contacts(modeladmin, request, queryset):
    flow = Flow.from_client_secrets_file(
        'contacts/client_secret.json',
        scopes=SCOPES)
    flow.redirect_uri = "http://localhost:8000/callback"
    authorization_url, state = flow.authorization_url(
        access_type='offline',
        include_granted_scopes='true')
    return HttpResponseRedirect(authorization_url)

def auth_callback(request):
    code = request.GET.get('code')
    flow = Flow.from_client_secrets_file(
        'contacts/client_secret.json',
        scopes=SCOPES)
    flow.redirect_uri = "http://localhost:8000"
    flow.fetch_token(code=code)
    creds = flow.credentials
    send_email(creds)

def send_email(creds):
    message_body = "Test content"
    message = MIMEMultipart()
    message['to'] = 'test.address@example.com'
    message.attach(MIMEText(message_body, "plain"))
    try:
        service = build('gmail', 'v1', credentials=creds)
        message = {'message': {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}}
        service.users().drafts().create(userId='me', body=message).execute()
    except HttpError as err:
        print(err)

...

class ContactAdmin(admin.ModelAdmin):
    actions = [emails_contacts]

注:目前仅测试创建邮件,暂未使用查询集数据填充内容

urls.py 代码

... 

from contacts.admin import auth_callback

urlpatterns = [
    path('callback/', auth_callback, name='oauth_callback'),
    path('admin/', admin.site.urls),
...

client_secret.json 配置

{"web":{"client_id":"....apps.googleusercontent.com","project_id":"...","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","...":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"...","redirect_uris":["http://localhost:8000/callback","http://localhost:8000/callback/","http://localhost/callback","http://localhost/callback/","http://localhost:8000/","http://localhost:8000","http://localhost","http://localhost/"]}}

注:已配置多个redirect_uri确保兼容性


错误信息

在/callback/路径触发CustomOAuth2Error,错误类型为redirect_uri_mismatch(Bad Request),报错位置为flow.fetch_token(code=code)行。


解决方案

1. 统一OAuth流程中的redirect_uri

OAuth授权请求和令牌获取阶段的redirect_uri必须完全一致。当前email_contacts函数中设置的是http://localhost:8000/callback,但auth_callback中错误设置为http://localhost:8000,这是核心问题。修改auth_callback:

def auth_callback(request):
    code = request.GET.get('code')
    flow = Flow.from_client_secrets_file(
        'contacts/client_secret.json',
        scopes=SCOPES)
    flow.redirect_uri = "http://localhost:8000/callback"  # 和授权阶段保持一致
    flow.fetch_token(code=code)
    creds = flow.credentials
    send_email(creds)
    # 重定向回Admin页面,给用户反馈
    return HttpResponseRedirect('/admin/contacts/contact/')

2. 修正函数名拼写错误

Admin动作中注册的emails_contacts与实际定义的函数名email_contacts不符,会导致动作无法加载。修改ContactAdmin:

class ContactAdmin(admin.ModelAdmin):
    actions = [email_contacts]  # 修正函数名

3. 验证Google Cloud配置的URI一致性

确认Google Cloud Console中OAuth 2.0客户端ID的已授权重定向URI列表里,http://localhost:8000/callback(注意结尾斜杠是否一致)已正确添加,避免因细微格式差异导致不匹配。

4. 补充state参数验证(可选但推荐)

添加state参数验证可防止CSRF攻击,提升安全性:

def email_contacts(modeladmin, request, queryset):
    flow = Flow.from_client_secrets_file(
        'contacts/client_secret.json',
        scopes=SCOPES)
    flow.redirect_uri = "http://localhost:8000/callback"
    authorization_url, state = flow.authorization_url(
        access_type='offline',
        include_granted_scopes='true')
    request.session['oauth_state'] = state  # 将state存入session
    return HttpResponseRedirect(authorization_url)

def auth_callback(request):
    code = request.GET.get('code')
    state = request.GET.get('state')
    # 验证state参数一致性
    if state != request.session.get('oauth_state'):
        return HttpResponse("无效的state参数", status=400)
    
    flow = Flow.from_client_secrets_file(
        'contacts/client_secret.json',
        scopes=SCOPES,
        state=state)
    flow.redirect_uri = "http://localhost:8000/callback"
    flow.fetch_token(code=code)
    creds = flow.credentials
    send_email(creds)
    return HttpResponseRedirect('/admin/contacts/contact/')

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 23:03:11