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

Google OAuth刷新令牌能否跨多域名使用?令牌失效问题排查

问题分析与解决方案

核心问题

三个站点共用同一credentials.json和刷新令牌(Refresh Token),且均已将站点域名加入OAuth重定向URL列表,但所有站点的访问令牌(Access Token)过期后,调用fetchAccessTokenWithRefreshToken时均触发Invalid Grant - Bad request错误。手动重新授权生成的新令牌可正常使用约1个月,但到期后仍无法自动刷新。

可能原因

1. 跨站点复用刷新令牌的安全限制

Google OAuth的刷新令牌与**生成该令牌时的授权请求上下文(尤其是重定向URL)**绑定。即使所有站点的重定向URL都在允许列表中,若三个站点复用的是某一个站点授权生成的刷新令牌,其他站点在使用该令牌刷新时,Google的安全校验会识别到请求来源的上下文不一致,从而拒绝刷新请求,返回invalid_grant。

2. 刷新令牌自动失效的常见场景

Google的刷新令牌本身没有默认1个月过期的规则,但以下情况会导致令牌失效:

  • 用户在Google账号的「安全中心」主动撤销了对该应用的授权
  • OAuth凭证的Client Secret被重置过
  • 用户的Google账号密码发生变更
  • 同一用户对该应用的有效刷新令牌数量超过50个(Google默认限制,超出后最早生成的令牌会被自动作废)
  • 应用请求的权限范围(Scopes)发生变更,但未重新获取用户授权

3. 代码层面的潜在问题

  • 检查tokens.json中存储的刷新令牌是否完整,是否在跨站点复制过程中出现字符丢失或篡改
  • 当前代码未对fetchAccessTokenWithRefreshToken的返回结果做错误判断,若刷新失败,仍会尝试将无效的令牌写入文件,导致后续请求重复报错
  • 确认授权流程中setAccessType('offline')和setPrompt('select_account consent')参数已正确设置,确保能获取到长期有效的刷新令牌

解决方案

  1. 为每个站点单独生成刷新令牌:每个站点各自执行一次完整的用户授权流程,生成对应自身重定向URL的刷新令牌,避免跨站点复用同一令牌。
  2. 添加刷新错误处理逻辑:在代码中对fetchAccessTokenWithRefreshToken的返回结果进行校验,若返回错误(如invalid_grant),则触发重新授权流程,示例修改如下:
if ($client->getRefreshToken()) {
    $newToken = $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    if (array_key_exists('error', $newToken)) {
        // 刷新失败,触发重新授权
        $authUrl = $client->createAuthUrl();
        printf("Open the following link in your browser:\n%s\n", $authUrl);
        print 'Enter verification code: ';
        $authCode = trim(fgets(STDIN));
        $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
        $client->setAccessToken($accessToken);
        if (array_key_exists('error', $accessToken)) {
            throw new Exception(join(', ', $accessToken));
        }
    } else {
        $client->setAccessToken($newToken);
    }
}
  1. 检查OAuth凭证配置:登录Google Cloud Console,确认所有站点的重定向URL已正确添加,OAuth凭证状态正常,Client Secret未被重置。
  2. 验证刷新令牌有效性:可通过Google OAuth的令牌信息接口检查刷新令牌是否有效(PHP客户端可调用对应方法完成验证)。

附用户提供的原代码

public function getClient(){

    $client = new \Google_Client();
    $client->setApplicationName('Gmail API PHP Quickstart');
    $client->setScopes(\Google_Service_Gmail::GMAIL_MODIFY);
    $client->setAuthConfig(APPPATH.'Config/Google/credentials.json');
    $client->setAccessType('offline');
    $client->setPrompt('select_account consent');

    $tokenPath = APPPATH.'Config/Google/tokens.json';

    $token_json = file_get_contents($tokenPath);

    if (!empty($token_json)) {
        $accessToken = json_decode($token_json, true);
        $client->setAccessToken($accessToken);
    }

    // If there is no previous token or it's expired.
    if ($client->isAccessTokenExpired()) {

        // Refresh the token if possible, else fetch a new one.
        if ($client->getRefreshToken()) {
            $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
        } else {
            // Request authorization from the user.
            $authUrl = $client->createAuthUrl();
            printf("Open the following link in your browser:\n%s\n", $authUrl);
            print 'Enter verification code: ';
            $authCode = trim(fgets(STDIN));

            // Exchange authorization code for an access token.
            $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
            $client->setAccessToken($accessToken);

            // Check to see if there was an error.
            if (array_key_exists('error', $accessToken)) {
                throw new Exception(join(', ', $accessToken));
            }
        }

        // Save the token to a file.
        if (!file_exists(dirname($tokenPath))) {
            mkdir(dirname($tokenPath), 0700, true);
        }

        $ok = file_put_contents($tokenPath, json_encode($client->getAccessToken()));
    }

return $client;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 17:17:29