如何在Django中通过OAuth使用用户Gmail账户发送邮件?
实现企业主Gmail账户授权及发票邮件发送(Django+DRF)
核心方案:使用Google OAuth2授权码流程
Web端必须采用OAuth2授权码流程替代桌面端的本地凭据文件方案,该流程要求企业主主动授权你的应用访问其Gmail账户,且授权凭证(access_token、refresh_token)可直接关联到对应的Organization实例,符合多用户隔离的需求。
步骤1:Google Cloud Console配置
- 新建项目,启用Gmail API
- 创建OAuth 2.0客户端ID,选择「Web应用」类型
- 配置授权回调URL:生产环境填
https://你的域名.com/api/gmail/oauth/callback,本地开发可填http://localhost:8000/api/gmail/oauth/callback - 下载客户端凭据JSON文件(包含
client_id、client_secret),存到Django项目的安全目录(禁止提交到版本控制)
步骤2:Django/DRF后端实现
安装依赖
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client
扩展Organization模型
在原有模型中添加字段存储Google OAuth凭证:
from django.db import models from django.contrib.auth.models import User class Organization(models.Model): # 原有字段保留 user = models.ForeignKey(User, on_delete=models.CASCADE) google_refresh_token = models.TextField(null=True, blank=True) google_access_token = models.TextField(null=True, blank=True) token_expiry = models.DateTimeField(null=True, blank=True)
实现授权视图
1. 生成授权URL的接口(供前端调用)
from django.http import JsonResponse from django.conf import settings from google_auth_oauthlib.flow import Flow import os def get_gmail_auth_url(request): if not request.user.is_authenticated: return JsonResponse({"error": "未登录"}, status=401) flow = Flow.from_client_secrets_file( os.path.join(settings.BASE_DIR, "安全目录/client_secret.json"), scopes=["https://www.googleapis.com/auth/gmail.send"], # 仅请求邮件发送权限,遵循最小权限原则 redirect_uri=settings.GOOGLE_OAUTH_REDIRECT_URI ) authorization_url, state = flow.authorization_url( access_type="offline", # 强制获取refresh_token,支持长期授权 prompt="consent" # 每次授权强制用户确认,避免缓存授权 ) request.session["oauth_state"] = state return JsonResponse({"authorization_url": authorization_url})
2. 授权回调处理视图
from django.shortcuts import redirect from django.conf import settings from google.auth.transport.requests import Request from google_auth_oauthlib.flow import Flow import os def gmail_oauth_callback(request): session_state = request.session.get("oauth_state") request_state = request.GET.get("state") if not session_state or session_state != request_state: return JsonResponse({"error": "无效的授权状态"}, status=400) flow = Flow.from_client_secrets_file( os.path.join(settings.BASE_DIR, "安全目录/client_secret.json"), scopes=["https://www.googleapis.com/auth/gmail.send"], redirect_uri=settings.GOOGLE_OAUTH_REDIRECT_URI, state=session_state ) flow.fetch_token(code=request.GET.get("code")) credentials = flow.credentials # 将凭证关联到当前用户的Organization org = Organization.objects.get(user=request.user) org.google_refresh_token = credentials.refresh_token org.google_access_token = credentials.token org.token_expiry = credentials.expiry org.save() # 重定向回前端成功页面,或返回JSON结果 return redirect("/dashboard?auth_success=true")
实现邮件发送函数
from googleapiclient.discovery import build from googleapiclient.errors import HttpError from google.oauth2.credentials import Credentials from django.conf import settings from email.mime.text import MIMEText import base64 def send_invoice_via_gmail(org, invoice_details): # 初始化凭证并刷新过期的access_token credentials = Credentials( token=org.google_access_token, refresh_token=org.google_refresh_token, client_id=settings.GOOGLE_CLIENT_ID, client_secret=settings.GOOGLE_CLIENT_SECRET, token_uri="https://oauth2.googleapis.com/token", scopes=["https://www.googleapis.com/auth/gmail.send"] ) if credentials.expired and credentials.refresh_token: credentials.refresh(Request()) # 更新数据库中的最新凭证 org.google_access_token = credentials.token org.token_expiry = credentials.expiry org.save() try: service = build("gmail", "v1", credentials=credentials) # 构造邮件内容 message = MIMEText(invoice_details["content"]) message["to"] = invoice_details["client_email"] message["subject"] = f"发票 #{invoice_details['invoice_id']}" # 编码为Gmail要求的base64格式 encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode() create_message = {"raw": encoded_message} # 发送邮件 send_result = service.users().messages().send(userId="me", body=create_message).execute() return {"success": True, "message_id": send_result["id"]} except HttpError as error: return {"success": False, "error": str(error)}
步骤3:前端极简实现
添加授权按钮并绑定逻辑:
<button id="authGmailBtn">授权Gmail账户发送发票</button> <script> document.getElementById("authGmailBtn").addEventListener("click", async () => { const res = await fetch("/api/gmail/auth-url", { method: "GET", credentials: "include" // 携带登录会话Cookie }); const data = await res.json(); if (data.authorization_url) { window.location.href = data.authorization_url; } else { alert("请先登录账号"); } }); </script>
关键注意事项
- 凭证安全:
refresh_token是长期授权凭证,建议使用django-cryptography等工具加密存储 - 权限控制:仅请求
gmail.send权限,不要申请全Gmail访问权限,提升用户信任度 - token刷新:access_token有效期仅1小时,必须在发送邮件前自动检查并刷新
- 本地测试:需在Google Cloud Console中将
http://localhost:8000添加到授权域名白名单
内容的提问来源于stack exchange,提问作者Parfait_D
相关产品推荐
相关产品推荐

