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

如何在Django中存储Google OAuth2令牌?解决refresh_token缺失问题

解决Google OAuth2无法获取Refresh Token的问题

问题出在前端使用的gapi默认采用隐式授权流程(Implicit Flow),该流程本身不返回refresh_token;同时即使切换到授权码流程,也需要配置特定参数才能触发Google返回refresh_token。下面是具体的解决方案:

1. 切换到授权码流程(Authorization Code Flow)

这是获取refresh_token的核心前提,隐式流程不支持长期令牌刷新。需要前端手动构造授权URL,而非依赖gapi的默认逻辑:

前端(Vue.js)授权跳转代码

// 构造Google授权URL
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${YOUR_GOOGLE_CLIENT_ID}&redirect_uri=${encodeURIComponent(window.location.origin + '/auth/google/callback')}&response_type=code&scope=${encodeURIComponent('openid email profile https://www.googleapis.com/auth/classroom.courses.readonly')}&access_type=offline&prompt=consent`;
window.location.href = authUrl;

关键参数说明:

  • response_type=code:指定使用授权码流程
  • access_type=offline:强制Google返回refresh_token(必须参数)
  • prompt=consent:确保每次授权都弹出确认框,避免因用户已授权过而不返回refresh_token(首次授权后可根据需求调整)
  • scope:必须包含Classroom所需权限(示例为只读课程权限,根据实际需求添加)

前端回调处理

在回调页面中提取授权码,发送给后端换取令牌:

// 回调页面的mounted钩子
const code = new URLSearchParams(window.location.search).get('code');
if (code) {
  axios.post('/auth/google/exchange-token', { code })
    .then(res => {
      // 保存后端返回的内部令牌
      localStorage.setItem('internal_token', res.data.access_token);
      window.location.href = '/';
    });
}

2. 后端处理令牌交换与存储

后端收到授权码后,调用Google的令牌接口换取完整的令牌信息(包含refresh_token),并存储到你的GoogleOAuth2Credentials模型中:

后端自定义令牌交换视图

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import requests
from django.conf import settings
from .models import GoogleOAuth2Credentials
from django.contrib.auth.models import User
from social_django.utils import load_strategy, load_backend
from oauth2_provider.models import AccessToken

class GoogleExchangeTokenView(APIView):
    def post(self, request):
        code = request.data.get('code')
        if not code:
            return Response({'error': '授权码不能为空'}, status=status.HTTP_400_BAD_REQUEST)
        
        # 调用Google令牌接口换取access_token、refresh_token
        token_payload = {
            'code': code,
            'client_id': settings.GOOGLE_CLIENT_ID,
            'client_secret': settings.GOOGLE_CLIENT_SECRET,
            'redirect_uri': settings.GOOGLE_REDIRECT_URI,
            'grant_type': 'authorization_code'
        }
        token_res = requests.post('https://oauth2.googleapis.com/token', data=token_payload)
        token_data = token_res.json()
        
        if 'error' in token_data:
            return Response(token_data, status=status.HTTP_400_BAD_REQUEST)
        
        # 获取用户信息,关联本地用户
        userinfo = requests.get(
            'https://openidconnect.googleapis.com/v1/userinfo',
            headers={'Authorization': f'Bearer {token_data["access_token"]}'}
        ).json()
        user, _ = User.objects.get_or_create(email=userinfo['email'], defaults={'username': userinfo['email'].split('@')[0]})
        
        # 存储Google令牌(支持更新已存在的记录)
        GoogleOAuth2Credentials.objects.update_or_create(
            user=user,
            defaults={
                'access_token': token_data['access_token'],
                'refresh_token': token_data.get('refresh_token'),  # 仅首次授权或重新授权时存在
                'id_token': token_data.get('id_token'),
                'scope': token_data['scope'].split(' ')
            }
        )
        
        # 生成后端内部令牌(复用drf-social-oauth2逻辑)
        strategy = load_strategy(request)
        backend = load_backend(strategy, 'google-oauth2', redirect_uri=settings.GOOGLE_REDIRECT_URI)
        backend.do_auth(token_data['access_token'])
        
        # 返回内部令牌给前端
        internal_token = AccessToken.objects.get(user=user, application__name='你的应用名称')
        return Response({
            'access_token': internal_token.token,
            'refresh_token': internal_token.refresh_token.token if internal_token.refresh_token else None
        })

3. 关键配置与注意事项

  • Google控制台配置:确保redirect_uri已添加到OAuth客户端的授权回调地址列表中,同时启用Google Classroom API
  • Refresh Token有效期:Google的refresh_token长期有效,但如果用户撤销权限或超过令牌限制(每个用户每个客户端最多50个),会失效,需要重新触发授权
  • 令牌加密:继续使用EncryptedTextField存储敏感令牌,避免明文泄露

内容的提问来源于stack exchange,提问作者Samuele B.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 08:42:40