PHP调用Google Gmail API发送邮件出现401未授权错误排查
代码问题排查与修复方案
直接触发401报错的核心问题
- 权限范围配置错误:当前
getClient方法中设置的请求范围为Google_Service_Gmail::GMAIL_READONLY,仅具备邮件读取权限,无调用邮件发送接口的资格,即使认证流程跑通,后续发信操作仍会触发权限拦截。需将scope替换为发信对应的权限值Google_Service_Gmail::GMAIL_SEND。注意因为权限范围从只读调整为发信,首次运行新代码时需要重新走一次登录流程完成新权限的授权,之前的旧授权记录因为scope不匹配无法复用。 - OAuth2凭证未绑定客户端实例:
getClient方法仅初始化了客户端的基础应用配置,全程未将有效访问令牌绑定到$client实例。打印$client能看到配置信息,是因为所有初始化参数都会存在实例属性中,但没有绑定有效令牌时,请求头不会携带合法的Authorization字段,直接触发"Login Required"的401报错。 - 配置文件结构不符合客户端加载规则:
credentials.json本是Google开发者后台下载的仅包含OAuth应用基础信息(client_id、client_secret等)的配置文件,手动将access_token写入该文件结构的操作不会被客户端识别,官方客户端不会从该文件读取自定义的token字段。
无中断使用的流程设计方案
要实现登录一次后长期稳定使用,不需要每次手动走登录流程,需要调整授权结果的存储逻辑:
- 因为代码中已设置
access_type=offline,首次授权完成时,返回结果会同时包含短期有效的access_token和长期有效的refresh_token,需要将两个值单独存储在服务端(本地文件、数据库、session均可),不能硬编码在应用配置文件中。 - 增加自动续期逻辑:
access_token有效期通常为1小时,过期后直接用存储的refresh_token调用官方接口换取新的access_token即可,不需要用户重复操作登录。
修复后的可运行代码参考
修正后的getClient方法(带token加载、自动续期)
function getClient() { $client = new Google_Client(); $client->setRedirectUri('http://site.com/oauth2callback.php'); $client->setApplicationName('Gmail API PHP'); // 替换为发信对应的权限范围 $client->setScopes(Google_Service_Gmail::GMAIL_SEND); $client->setAuthConfig('credentials.json'); $client->setAccessType('offline'); $client->setPrompt('select_account consent'); // 从本地单独存储的文件中读取授权token $tokenPath = 'oauth_token.json'; if (file_exists($tokenPath)) { $accessToken = json_decode(file_get_contents($tokenPath), true); $client->setAccessToken($accessToken); } // 检查token有效性,过期自动续期 if ($client->isAccessTokenExpired()) { // 存在refresh_token的情况下直接换发新token if ($client->getRefreshToken()) { $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken()); file_put_contents($tokenPath, json_encode($client->getAccessToken())); } else { // 无有效token时,跳转授权页完成首次登录 $authUrl = $client->createAuthUrl(); header('Location: ' . filter_var($authUrl, FILTER_SANITIZE_URL)); exit; } } return $client; }
授权回调页核心逻辑(oauth2callback.php)
负责接收授权码、换取有效token、落地存储:
$client = getClient(); if (isset($_GET['code'])) { // 用授权码换token $token = $client->fetchAccessTokenWithAuthCode($_GET['code']); // 把拿到的token(含refresh_token)写入本地存储 file_put_contents('oauth_token.json', json_encode($token)); // 存储完成后跳转回业务页面 header('Location: /send-message-page.php'); exit; }
发信方法的编码修正
Gmail接口要求发送的raw内容为URL安全格式的Base64编码,原代码直接用编码器生成的标准Base64存在兼容问题,需替换为以下逻辑:
public function sendMessage() { $client = self::getClient(); $service = new Google_Service_Gmail($client); $mailer = $service->users_messages; $message = (new \Swift_Message('Here is my subject')) ->setFrom('myemailaddress@myserver.com') ->setTo(['receiver@someserver.com' => 'Test Name']) ->setContentType('text/html') ->setCharset('utf-8') ->setBody('<h4>Here is my body</h4>'); // 替换原有的base64编码逻辑,生成URL安全格式的编码 $msg_raw = $message->toString(); $msg_base64 = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($msg_raw)); $message = new Google_Service_Gmail_Message(); $message->setRaw($msg_base64); $message = $mailer->send('me', $message); print_r($message); }
内容的提问来源于stack exchange,提问作者RedEclipse
相关产品推荐
相关产品推荐

