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

Gmail API离线使用refresh token过期重登报403权限不足错误

问题排查与解决方案

你遇到的403权限不足错误,核心是当前调用API使用的Access Token所关联的授权范围,不满足Gmail只读接口的调用要求。以下是具体排查步骤和修复方案:

排查步骤

  • 检查数据库存储的授权令牌对应的权限范围:查看office_google_token字段存储的JSON内容里的scope字段,确认是否包含https://www.googleapis.com/auth/gmail.readonly(即Google_Service_Gmail::GMAIL_READONLY对应的实际权限值)。如果缺少该范围,说明用户授权时并未同意Gmail读取权限,或者授权流程没有正确传递你申请的scope。
  • 检查应用在Google Cloud Console的发布状态:如果你的应用还处于「测试」状态,Google默认测试用户的Refresh Token有效期只有7天,到期就会自动作废,这就是你首次登录后只能正常运行一周的原因。
  • 检查重新授权流程的参数:确认用户重新登录授权时,是否强制触发了权限确认流程,有没有可能用户重登时默认复用了旧的、没有Gmail读取权限的授权记录。

代码修复方案

1. 修复令牌刷新逻辑

你现有代码存在逻辑错误:每次循环只要有Refresh Token就强制刷新,且刷新后没有将新的令牌存回数据库,同时逻辑顺序颠倒。参考修复后的代码片段:

$googleClient = new Google_Client();
$googleClient->setClientId('DELETED');
$googleClient->setClientSecret('DELETED');
$googleClient->setRedirectUri(explode('.', $_SERVER['HTTP_HOST'])[0] == 'dev' ? 'DEV_URI' : 'PROD_URI');
$googleClient->setAccessType('offline');
// 这里的scope要和授权入口的scope完全一致
$googleClient->addScope('email');
$googleClient->addScope('profile');
$googleClient->addScope(Google_Service_Gmail::GMAIL_READONLY);

$googleClient->setAccessToken($office->office_google_token);

// 先判断令牌是否过期,再决定是否刷新
if ($googleClient->isAccessTokenExpired()) {
    if (!empty($office->office_google_refresh_token)) {
        try {
            // 刷新令牌
            $newToken = $googleClient->refreshToken($office->office_google_refresh_token);
            // 刷新后必须将新的令牌存回数据库,如果返回了新的refresh_token也要同步更新
            if (isset($newToken['refresh_token'])) {
                $updateData['office_google_refresh_token'] = $newToken['refresh_token'];
            }
            $updateData['office_google_token'] = json_encode($newToken);
            $this->google_conn_model->updateOfficeToken($office->office_id, $updateData);
        } catch (\Exception $e) {
            // 刷新失败说明refresh_token已经失效,标记需要重新授权
            $this->debug(1, 'cronGmail/1', 'token for oauth: '.$office->office_google_login_oauth_id.' refresh failed');
            $this->google_conn_model->setTokenExpiredForOffice($office->office_id);
            $this->cronReport('cronGmail/1', $startTime, date('Y-m-d H:i:s', time()), 'token refresh failed, oauth: '.$office->office_google_login_oauth_id, 0);
            continue;
        }
    } else {
        // 没有refresh_token,直接标记过期
        $this->debug(1, 'cronGmail/1', 'token for oauth: '.$office->office_google_login_oauth_id.' is expired and no refresh token');
        $this->google_conn_model->setTokenExpiredForOffice($office->office_id);
        $this->cronReport('cronGmail/1', $startTime, date('Y-m-d H:i:s', time()), 'token expired without refresh token, oauth: '.$office->office_google_login_oauth_id, 0);
        continue;
    }
}

// 增加scope校验,避免权限不足
$currentScopes = $googleClient->getScopes();
if (!in_array(Google_Service_Gmail::GMAIL_READONLY, $currentScopes)) {
    $this->debug(1, 'cronGmail/1', 'token for oauth: '.$office->office_google_login_oauth_id.' missing gmail scope');
    $this->google_conn_model->setTokenExpiredForOffice($office->office_id);
    $this->cronReport('cronGmail/1', $startTime, date('Y-m-d H:i:s', time()), 'missing gmail scope, oauth: '.$office->office_google_login_oauth_id, 0);
    continue;
}

// 令牌有效且权限足够,再调用Gmail接口
$gmailService = new Google_Service_Gmail($googleClient);
$connectParams = ['maxResults' => 100];
$getMessagesResult = $gmailService->users_messages->listUsersMessages('me', $connectParams);

2. 修复用户授权入口逻辑

在用户跳转Google授权的代码位置,增加prompt参数,强制用户每次重登时都确认权限,确保获取到完整的授权范围:

// 授权入口添加这行配置
$googleClient->setPrompt('select_account consent');

3. 解决Refresh Token7天过期问题

如果需要Refresh Token长期有效,需要进入Google Cloud Console的OAuth同意屏幕配置页面,将应用从「测试」状态改为「正式发布」状态,按照要求完成Google的OAuth应用验证流程即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 14:09:01