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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 05:25:51