You need to enable JavaScript to run this app. PHP调用Google Gmail API发送邮件出现401未授权错误排查
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.08 14:15:06