Django应用获取自有Google Calendar读写权限遇认证问题
搞定Django中Google Calendar读写权限的认证问题
嗨,看你折腾了服务账号、客户端密钥都踩坑,API Explorer的弹窗授权又不符合后台需求,咱们就顺着你推测的Web Server Application场景,一步步把问题解决掉,同时也给你排查下服务账号的常见坑。
先说说服务账号方式为啥失败(针对Google Workspace用户)
如果你用的是企业Google Workspace账号,服务账号其实是可行的,但你大概率没做域范围委派——这是服务账号访问用户日历的关键步骤:
- 登录Google Cloud控制台,找到你的服务账号,进入「权限」标签,点击「创建委派」
- 输入Calendar API的读写范围:
https://www.googleapis.com/auth/calendar - 接着登录Google Workspace管理员后台,在「安全」→「API控制」→「域名范围委派」里,添加服务账号的客户端ID,把刚才的API范围填进去
- 代码里必须指定要模拟的用户邮箱(就是你要访问的日历所属账号),示例代码如下:
没加from google.oauth2 import service_account SCOPES = ['https://www.googleapis.com/auth/calendar'] SERVICE_ACCOUNT_FILE = '你的服务账号密钥文件路径.json' credentials = service_account.Credentials.from_service_account_file( SERVICE_ACCOUNT_FILE, scopes=SCOPES, subject='你的日历所有者邮箱@xxx.com')subject参数的话,服务账号根本没权限访问具体用户的日历,这是很多人踩的坑!
个人账号/非Workspace场景:用Web Server离线授权实现无弹窗后台访问
如果是个人Google账号,服务账号没法直接用,那Web Server的授权流程可以实现一次性授权后长期用刷新令牌,彻底摆脱弹窗:
1. 先在Google Cloud配置好客户端ID
- 进入Google Cloud控制台,创建OAuth 2.0客户端ID,选择「Web应用」类型
- 填写「已授权的重定向URI」,比如
https://你的Django域名.com/google-calendar/callback(要和后面Django的视图路由对应上) - 下载客户端密钥JSON文件,放到Django项目的安全目录里(别提交到代码仓库)
2. Django里实现授权和回调逻辑
(1)写个视图生成授权跳转链接
from django.shortcuts import redirect from google_auth_oauthlib.flow import Flow SCOPES = ['https://www.googleapis.com/auth/calendar'] CLIENT_SECRETS_FILE = '你的客户端密钥文件路径.json' def google_calendar_auth(request): flow = Flow.from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=SCOPES, redirect_uri='https://你的Django域名.com/google-calendar/callback') # 必须设置access_type为offline,才能拿到刷新令牌 authorization_url, state = flow.authorization_url( access_type='offline', include_granted_scopes='true') # 把state存到session里,回调时用来验证请求合法性 request.session['state'] = state return redirect(authorization_url)
(2)回调视图获取并保存令牌
from django.http import HttpResponse from google_auth_oauthlib.flow import Flow import json def google_calendar_callback(request): state = request.session.get('state') flow = Flow.from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=SCOPES, state=state, redirect_uri='https://你的Django域名.com/google-calendar/callback') # 从回调请求里获取令牌 flow.fetch_token(authorization_response=request.build_absolute_uri()) # 拿到包含刷新令牌的凭证,一定要加密存储(示例用文件,实际推荐存数据库并加密) credentials = flow.credentials creds_data = { 'token': credentials.token, 'refresh_token': credentials.refresh_token, 'token_uri': credentials.token_uri, 'client_id': credentials.client_id, 'client_secret': credentials.client_secret, 'scopes': credentials.scopes } with open('encrypted_credentials.json', 'w') as f: json.dump(creds_data, f) return HttpResponse("授权搞定!现在后台就能访问Google Calendar了")
(3)用保存的令牌访问Calendar API
from google.oauth2.credentials import Credentials from googleapiclient.discovery import build import json from google.auth.transport.requests import Request def access_calendar(): # 读取保存的凭证 with open('encrypted_credentials.json', 'r') as f: creds_data = json.load(f) credentials = Credentials.from_authorized_user_info(creds_data) # 令牌过期自动刷新,不用手动管 if credentials.expired and credentials.refresh_token: credentials.refresh(Request()) # 构建Calendar服务实例 service = build('calendar', 'v3', credentials=credentials) # 测试下获取日历列表 calendars = service.calendarList().list().execute() print(calendars)
几个关键提醒
- 个人账号只能用Web Server离线授权方式,服务账号对个人账号无效
- 刷新令牌一定要加密存储,绝对不能明文放在代码或配置里
- 本地测试可以用
http://localhost:8000作为重定向URI,但正式环境必须用HTTPS - API Explorer的弹窗是交互式授权,只适合测试,后台应用必须靠离线授权拿到refresh_token才能长期运行
内容的提问来源于stack exchange,提问作者Davy
相关产品推荐
相关产品推荐

