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
相关产品推荐
相关产品推荐

