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

PHPMailer使用Gmail XOAUTH2发信如何自动获取新refresh_token

问题核心结论

首先明确两点:

  1. 你遇到的refresh_token频繁过期问题,本质是Google OAuth应用配置错误导致的,正常配置下的refresh_token有效期接近永久(仅当账号密码修改、手动撤销授权、连续6个月未使用时才会失效),不需要频繁更新。
  2. Google OAuth2授权流程必须经过账号持有人的显式交互确认,不存在纯PHP后端无人工操作自动获取新refresh_token的方案,这是OAuth协议的安全机制决定的,无法绕过。
第一步:修复OAuth配置,拿到长期有效refresh_token

按以下检查调整配置,从根源解决token频繁过期问题:

  • 进入Google Cloud控制台对应项目的「OAuth同意屏幕」页面,将应用发布状态从测试中修改为生产中。测试状态下颁发的refresh_token固定只有7天有效期,切换为生产状态后(个人账号自建的内部发信应用不需要提交Google审核),颁发的refresh_token即为长期有效。
  • 发起授权请求时必须携带两个参数:access_type=offline(告诉Google返回可用于刷新的refresh_token)、prompt=consent(强制显示授权确认页,确保第一次授权就能拿到refresh_token,否则Google默认不返回该字段)。
  • 授权时仅申请必要权限:https://www.googleapis.com/auth/gmail.send,不要申请多余的邮箱访问权限,降低安全风险。
第二步:实现token自动刷新与持久化,替换硬编码逻辑

你当前代码硬编码refresh_token的写法无法自动更新token,需要把token持久化存储(存到web无法访问的配置文件/数据库/缓存中),在发信时自动检测access_token有效期,过期则自动用refresh_token换取新的access_token,若刷新时Google返回了新的refresh_token(仅在安全轮换场景下出现)则自动持久化保存。
完整可落地代码如下:

<?php

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\OAuth;
use League\OAuth2\Client\Provider\Google;

// 配置项,建议放到独立的配置文件中
$config = [
    'clientId' => '你的Google OAuth客户端ID',
    'clientSecret' => '你的Google OAuth客户端密钥',
    'senderEmail' => '发信用的Gmail地址',
    'tokenStoragePath' => __DIR__ . '/../secure_storage/gmail_oauth_token.json' // 必须放到web根目录外,避免被公网访问
];

/**
 * 读取本地存储的OAuth凭证
 */
function loadOAuthToken(string $path): array
{
    if (!file_exists($path)) {
        throw new \RuntimeException("OAuth凭证文件不存在,请先完成首次授权流程");
    }
    $content = file_get_contents($path);
    $token = json_decode($content, true);
    if (json_last_error() !== JSON_ERROR_NONE || !isset($token['accessToken'])) {
        throw new \RuntimeException("OAuth凭证文件格式损坏");
    }
    return $token;
}

/**
 * 保存更新后的OAuth凭证
 */
function saveOAuthToken(string $path, array $token): void
{
    file_put_contents($path, json_encode($token, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
    chmod($path, 0600); // 设置严格权限,仅服务器进程可读可写
}

/**
 * 获取有效的AccessToken,过期自动刷新
 */
function getValidAccessToken(array $config, array $storedToken): array
{
    $provider = new Google([
        'clientId' => $config['clientId'],
        'clientSecret' => $config['clientSecret']
    ]);

    // access_token未过期,提前60秒判定需要刷新,避免请求过程中失效
    if (isset($storedToken['expires']) && $storedToken['expires'] > time() + 60) {
        return $storedToken;
    }

    // 过期则用refresh_token换新的access_token
    $newToken = $provider->getAccessToken('refresh_token', [
        'refresh_token' => $storedToken['refreshToken']
    ]);

    // 组装要保存的token结构
    $updatedToken = [
        'accessToken' => $newToken->getToken(),
        'refreshToken' => $newToken->getRefreshToken() ?? $storedToken['refreshToken'], // 兼容Google返回新refresh_token的轮换场景
        'expires' => $newToken->getExpires()
    ];

    // 持久化保存新token
    saveOAuthToken($config['tokenStoragePath'], $updatedToken);

    return $updatedToken;
}

function sendEmail(array $config, string $toEmail, string $title, string $detail): ?bool
{
    try {
        // 加载存储的token并自动刷新为有效状态
        $storedToken = loadOAuthToken($config['tokenStoragePath']);
        $validToken = getValidAccessToken($config, $storedToken);

        $mail = new PHPMailer(true);
        // 这里填写你原来的SMTP配置、发件人、收件人、邮件内容等逻辑
        // $mail->isSMTP();
        // $mail->Host = 'smtp.gmail.com';
        // $mail->SMTPAuth = true;
        // $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
        // $mail->Port = 465;
        // $mail->setFrom($config['senderEmail'], '站点发信名称');

        $provider = new Google([
            'clientId' => $config['clientId'],
            'clientSecret' => $config['clientSecret']
        ]);

        $mail->setOAuth(new OAuth([
            'provider' => $provider,
            'clientId' => $config['clientId'],
            'clientSecret' => $config['clientSecret'],
            'refreshToken' => $validToken['refreshToken'],
            'userName' => $config['senderEmail'],
        ]));

        $mail->addAddress($toEmail);
        $mail->Subject = $title;
        $mail->Body = $detail;

        return $mail->send();
    } catch (\Throwable $th) {
        // 在这里加告警逻辑:如果异常信息包含token过期、认证失败类关键词,立即通过备用渠道(短信/企业微信/其他非Gmail邮箱)通知管理员处理
        // 正常配置下该类故障触发频率极低,半年到数年才可能出现一次,管理员重新跑一次首次授权流程即可恢复
        return null;
    }
}
首次授权流程(仅需执行1次)

你需要写一个临时的授权脚本(执行完立即删除,不要长期留在生产环境),完成第一次授权拿到长期refresh_token:

  1. 提前创建好web根目录外的secure_storage目录,设置权限为服务器进程可读写。
  2. 新建临时文件get_gmail_token.php放到web可访问目录,代码如下:
<?php
require __DIR__ . '/vendor/autoload.php';
use League\OAuth2\Client\Provider\Google;

$config = [
    'clientId' => '你的Google OAuth客户端ID',
    'clientSecret' => '你的Google OAuth客户端密钥',
    'redirectUri' => '这个脚本的公网访问地址,比如https://你的域名/get_gmail_token.php',
    'tokenSavePath' => __DIR__ . '/../secure_storage/gmail_oauth_token.json'
];

$provider = new Google([
    'clientId'     => $config['clientId'],
    'clientSecret' => $config['clientSecret'],
    'redirectUri'  => $config['redirectUri'],
    'scopes'       => ['https://www.googleapis.com/auth/gmail.send'],
]);

if (!isset($_GET['code'])) {
    // 跳转Google授权页
    $authUrl = $provider->getAuthorizationUrl([
        'access_type' => 'offline',
        'prompt' => 'consent'
    ]);
    header("Location: {$authUrl}");
    exit;
} else {
    // 授权回调,拿到token保存
    $token = $provider->getAccessToken('authorization_code', [
        'code' => $_GET['code']
    ]);
    $tokenData = [
        'accessToken' => $token->getToken(),
        'refreshToken' => $token->getRefreshToken(),
        'expires' => $token->getExpires()
    ];
    file_put_contents($config['tokenSavePath'], json_encode($tokenData, JSON_PRETTY_PRINT));
    chmod($config['tokenSavePath'], 0600);
    echo "Token获取成功,请立即删除该临时脚本";
}
  1. 浏览器访问这个临时脚本的地址,登录发信用的Gmail账号完成授权,看到成功提示后立即删除该临时文件即可。
故障兜底方案

按以上配置完成后,正常发信过程中access_token会自动刷新,不需要人工干预。如果真的遇到refresh_token失效的极端场景(比如账号改密、手动撤销授权、超过6个月未发信),代码捕获到认证异常后会触发告警,你只需要重新跑一次上面的临时授权脚本即可恢复,全程耗时不超过1分钟,完全可以避免用户反馈才发现故障的问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 21:24:19