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

如何解决Google Apps Script中的‘身份验证范围不足’错误?

问题描述

我正在开发一个支持全域委派的Google Apps Script,目的是读取Google Workspace域内用户的Gmail详情,但即使访问自己的Gmail,脚本执行失败,报错如下:

"message": "Request had insufficient authentication scopes."
"errors ... ""message": "Insufficient Permission"
"reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT"

作为域超级管理员,我已完成以下操作:

  • 脚本已关联GCP标准项目
  • 创建了服务账号,我拥有的角色包括:Project IAM Admin、Service account admin、Service account user、Service Account Token Creator
  • 已引入OAuth2库
  • 每次修改后都会重置授权,强制重新请求授权

使用的OAuth 2.0范围:

  • https://www.googleapis.com/auth/script.external_request
  • https://www.googleapis.com/auth/gmail.labels(Gmail API)
  • https://www.googleapis.com/auth/admin.directory.user.readonly(Admin SDK API,当前未使用)

上述范围已在以下位置配置:

  • 项目清单(appsscript.json)
  • 项目OAuth同意屏幕
  • 全域委派(管理控制台 > 安全 > API控制 > 全域委派)
  • GCP项目已启用Gmail、Admin SDK API
  • 服务账号已获得Gmail和外部请求的“可信”访问权限(管理控制台 > 安全 > 访问和数据控制 > API控制 > 管理第三方应用访问 > 应用访问控制),未配置Admin SDK权限
  • Code.gs中setScope仅引用前两个范围

原脚本代码:

// Email address of the user to impersonate.
var USER_EMAIL = 'it@domain.org'; // use my address for testing the simplest case

function listLabels() {
  /**
   * References: 
   * https://developers.google.com/gmail/api/reference/rest/v1/users.labels/list
   * https://gmail.googleapis.com/$discovery/rest?version=v1
   * Similar script: https://developers.google.com/apps-script/advanced/gmail#list_label_information
   */
  try {
    const userID = '1x0gk371hhfknr'; // use my userID for now;
    var service = getService_('listLabels');    
    if (service.hasAccess()) {
      var url = 'https://gmail.googleapis.com/gmail/v1/users/' + userID + '/labels';
      Logger.log(url);
// Script fails here:
      var response = UrlFetchApp.fetch(
        url, {
          headers: { Authorization: 'Bearer ' + service.getAccessToken() },
          muteHttpExceptions: true
        }
      );
      Logger.log('response: '+ response);
      var result = JSON.parse(response.getContentText());
      Logger.log("result['labels']: " + JSON.stringify(result['labels'], null, 2)); 
      for (let i = 0; i < result['labels'].length; i++) {
        const label = result['labels'][i];
        Logger.log(JSON.stringify(label));
      }
    } else {
      Logger.log( 'Service lacks access' );
    }
  } catch (err) {
    Logger.log('listLabels failed with: ' + err);
  }
}

function getService_( serviceName ) {
/*
 * From https://github.com/googleworkspace/apps-script-oauth2/blob/main/samples/GoogleServiceAccount.gs
 * Implements domain-wide delegation 
 */

  return OAuth2.createService(serviceName)
    .setTokenUrl('https://oauth2.googleapis.com/token')
    .setPrivateKey(PRIVATE_KEY)
    .setIssuer(CLIENT_EMAIL)
    .setSubject(USER_EMAIL) // name of the user to impersonate

    // Set the property store where authorized tokens should be persisted.
    .setPropertyStore(PropertiesService.getScriptProperties())

    .setScope(
      "https://www.googleapis.com/auth/script.external_request",
      "https://www.googleapis.com/auth/gmail.labels"
    )
}

// Private key and client email of the service account. (I present fake values here.)
const PRIVATE_KEY =
    "-----BEGIN PRIVATE KEY-----\nMIIEv ... YNmQGU19FAcc=\n-----END PRIVATE KEY-----\n";
const CLIENT_EMAIL = "fifth-try@ ... .iam.gserviceaccount.com";

解决方案

核心问题排查与修复

  1. setScope参数格式问题
    OAuth2库的setScope方法若传递多个范围,需使用空格分隔的单个字符串,而非多个独立参数。原代码中传递两个参数的方式可能导致范围未正确合并,令牌仅包含部分权限。

  2. 用户ID格式错误
    Gmail API中,用户ID可直接使用'me'(代表当前 impersonate 的用户)或用户邮箱,无需使用内部ID。使用内部ID可能导致权限匹配异常。

  3. 清除旧令牌
    即使重置授权,脚本属性存储中可能仍保留旧的令牌数据。需手动清除已存储的令牌,确保新令牌使用正确的范围生成。

修正后的代码

// Email address of the user to impersonate.
var USER_EMAIL = 'it@domain.org'; // 测试用自己的邮箱

function listLabels() {
  try {
    var service = getService_('listLabels');    
    if (service.hasAccess()) {
      // 使用'me'指代当前 impersonate 的用户,无需手动指定内部ID
      var url = 'https://gmail.googleapis.com/gmail/v1/users/me/labels';
      Logger.log(url);
      
      var response = UrlFetchApp.fetch(
        url, {
          headers: { Authorization: 'Bearer ' + service.getAccessToken() },
          muteHttpExceptions: true
        }
      );
      Logger.log('response: '+ response);
      var result = JSON.parse(response.getContentText());
      
      if (result.labels) {
        Logger.log("labels: " + JSON.stringify(result.labels, null, 2)); 
        result.labels.forEach(label => Logger.log(JSON.stringify(label)));
      } else {
        Logger.log("No labels found or error: " + JSON.stringify(result));
      }
    } else {
      Logger.log( 'Service lacks access' );
      // 打印授权错误详情
      Logger.log(service.getLastError());
    }
  } catch (err) {
    Logger.log('listLabels failed with: ' + err);
  }
}

function getService_( serviceName ) {
  return OAuth2.createService(serviceName)
    .setTokenUrl('https://oauth2.googleapis.com/token')
    .setPrivateKey(PRIVATE_KEY)
    .setIssuer(CLIENT_EMAIL)
    .setSubject(USER_EMAIL) // 要 impersonate 的用户邮箱
    .setPropertyStore(PropertiesService.getScriptProperties())
    // 使用空格分隔的单个字符串传递多个范围
    .setScope("https://www.googleapis.com/auth/script.external_request https://www.googleapis.com/auth/gmail.labels")
}

// 服务账号私钥和客户端邮箱(替换为真实值)
const PRIVATE_KEY =
    "-----BEGIN PRIVATE KEY-----\nMIIEv ... YNmQGU19FAcc=\n-----END PRIVATE KEY-----\n";
const CLIENT_EMAIL = "fifth-try@ ... .iam.gserviceaccount.com";

额外验证步骤

  • 确认全域委派中添加的范围与脚本中使用的完全一致,无拼写错误
  • 检查GCP项目中Gmail API是否已正确启用(可在GCP控制台的API库中确认)
  • 执行脚本前,先运行以下代码清除旧令牌:
function clearTokens() {
  PropertiesService.getScriptProperties().deleteAllProperties();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 05:30:59