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

如何使用Django allauth刷新Google Access Token无需用户重登录

如何在Django allauth中自动刷新Google Access Token(无需用户重新登录)

背景与现有配置

Google Access Token默认有效期1小时,我已经通过Django allauth配置了谷歌登录,并添加了日历只读权限,配置如下:

THIRD_PARTY_APPS = [
    # ...
    "allauth.socialaccount",
    "allauth.socialaccount.providers.google",
]

SITE_ID = 1

SOCIALACCOUNT_PROVIDERS = {
    "google": {
        "SCOPE": [
            "profile",
            "email",
            "https://www.googleapis.com/auth/calendar.readonly",
        ],
        "AUTH_PARAMS": {
            "access_type": "offline",
        },
    }
}

SOCIALACCOUNT_STORE_TOKENS = True

从数据库读取令牌并创建Google Credentials对象的代码:

# 获取用户的谷歌令牌
token = SocialToken.objects.get(
    account__user=request.user, account__provider="google"
)
client_id = env("GOOGLE_CLIENT_ID", default="")
client_secret = env("GOOGLE_CLIENT_SECRET", default="")

from google.oauth2.credentials import Credentials
creds = Credentials(
    token=token.token,
    refresh_token=token.token_secret,
    token_uri="https://oauth2.googleapis.com/token",
    client_id=client_id,
    client_secret=client_secret,
)

令牌未过期时,能正常调用谷歌日历API获取事件,但令牌过期后调用creds.refresh(...)无法成功刷新,需要解决这个问题。

解决方案

1. 确认refresh_token已正确获取并存储

先检查数据库中SocialToken表的token_secret字段是否有值:

  • 只有用户第一次授权时(且配置了access_type=offline),谷歌才会返回refresh_token。如果之前未配置offline,需要让用户重新授权一次(可通过allauth的重新连接功能触发)。
  • 确保SOCIALACCOUNT_STORE_TOKENS=True,这是allauth将令牌存储到数据库的必要配置。

2. 正确调用刷新方法并更新数据库令牌

creds.refresh()需要传入请求对象,使用google.auth.transport.requests.Request()即可。刷新成功后,必须将新的access token(以及可能更新的refresh_token)保存回数据库,否则下次请求仍会使用旧令牌。

修改异常处理逻辑,加入刷新流程:

from googleapiclient.discovery import build
from datetime import datetime
from google.auth.transport.requests import Request

service = build("calendar", "v3", credentials=creds)
try:
    events_result = (
        service.events()
        .list(
            calendarId="primary",
            timeMin=datetime.now().date().isoformat() + "Z",
            maxResults=30,
            singleEvents=True,
            orderBy="startTime",
        )
        .execute()
    )
except Exception as e:
    print("Google API Error")
    if e.status_code == 403:
        if e.reason == "Request had insufficient authentication scopes.":
            print("用户需要授予日历权限")
    
    # 尝试刷新令牌
    if creds.expired and creds.refresh_token:
        try:
            # 执行刷新
            creds.refresh(Request())
            # 更新数据库中的令牌
            token.token = creds.token
            # 部分场景下refresh_token可能更新,保险起见同步
            if creds.refresh_token:
                token.token_secret = creds.refresh_token
            token.save()
            
            # 刷新成功后重新请求
            service = build("calendar", "v3", credentials=creds)
            events_result = service.events().list(
                calendarId="primary",
                timeMin=datetime.now().date().isoformat() + "Z",
                maxResults=30,
                singleEvents=True,
                orderBy="startTime",
            ).execute()
            # 继续处理events_result
        except Exception as refresh_e:
            print(f"令牌刷新失败: {refresh_e}")
            # 刷新失败时,可引导用户重新授权
    else:
        print("无有效refresh_token,无法自动刷新")

3. 主动检查令牌状态,提前刷新

不要等API报错再处理,在创建Credentials对象后主动检查是否过期,提前刷新:

# 创建creds后立即检查
from google.auth.transport.requests import Request

if creds.expired and creds.refresh_token:
    creds.refresh(Request())
    # 更新数据库
    token.token = creds.token
    if creds.refresh_token:
        token.token_secret = creds.refresh_token
    token.save()

# 之后再构建服务调用API
service = build("calendar", "v3", credentials=creds)

额外注意事项

  • SocialToken的expires_at字段由allauth自动维护,Credentials对象会根据这个字段判断令牌是否过期,确保该字段值正确。
  • 如果refresh_token失效(比如用户撤销了应用权限),刷新会失败,此时需要引导用户重新登录授权。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 02:45:18