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

Gmail签名AppScript新用户授权失败问题求助

Gmail签名AppScript新用户报错:Access not granted or expired 排查与解决

旧用户账号可正常执行脚本更新Gmail签名,但同OU、同类型的新创建用户执行时返回错误:
Error: Access not granted or expired. Service_.getAccessToken @ Service.gs:518
删除并重新创建服务账号后问题暂时解决,但不久后复现。每月仅为单个用户执行一次脚本,排除配额问题。相关脚本代码如下:

function go() {  
  var pageToken;
  var page;

  do {
    page = AdminDirectory.Users.list({
      domain: 'domain.com',
      orderBy: 'familyName',
      query: 'email=user@domain.com',
      maxResults: 500,
      pageToken: pageToken,
      projection: 'full',
      // query: "email=user@domain.com"
      // query: 'orgUnitPath=/Mentors'
    });
    if (page.users) {
      page.users.forEach( function (user){
        if (accountsToIgnore.indexOf(user.primaryEmail) == -1) {

        var service = getOAuthService(user.primaryEmail);
        // Pull in the signatire template file contents into this variable 
        var signatureTemplate = HtmlService.createHtmlOutputFromFile("signature").getContent();

          // Set up a userData variable, with some blank defaults as backups  
          var userData = {
            email: user.primaryEmail,
            firstName: user.name.givenName,
            lastName: user.name.familyName,
            jobTitle: "",
            showJobTitle: true,
            workingHours: "",
            directPhone: ""
          };
          if (typeof user.customSchemas !== 'undefined') { // Email sig settings are set
            if (typeof user.customSchemas.Email_signature !== 'undefined') {

              if (typeof user.customSchemas.Email_signature.Show_job_title_in_signature !== 'undefined' && user.customSchemas.Email_signature.Show_job_title_in_signature == false) {
                userData.showJobTitle = false; 
              }

              if (typeof user.customSchemas.Email_signature.Working_Hours_Description !== 'undefined' && user.customSchemas.Email_signature.Working_Hours_Description != "") {
                userData.workingHours = "<br /><br /><i>"+user.customSchemas.Email_signature.Working_Hours_Description+"</i><br />";
              }

            }
          }

          if (user.hasOwnProperty('organizations') && user.organizations[0].hasOwnProperty('title') && typeof user.organizations[0].title !== "undefined" && userData.showJobTitle == true) {
            userData.jobTitle = user.organizations[0].title+"<br />";
          }

          if (user.hasOwnProperty('phones') && Array.isArray(user.phones) && user.phones.length >0) {
            for (var p = 0; p < user.phones.length; p++) {
              if (user.phones[p].customType == "Google Voice") {
              // Depending on where in the world you are, you may need to adjust this formatting for your own needs... This replaces the +44 UK country code with a local "0" and adds a space after the local area code for formatting.
               userData.directPhone = "<br />D: " + user.phones[p].value.replace('+44', '0').replace('1158', '1158 '); 
              }
            }
          }

          // Replace the placeholders as seen in the signature.html file with the actual data from the userData variable set up earlier. 
          var userSig = signatureTemplate
          .replace(/(\r\n|\n|\r)/gm, "")
          .replace(/{email}/g, userData.email)
          .replace(/{firstName}/g, userData.firstName)
          .replace(/{lastName}/g, userData.lastName)
          .replace(/{jobTitle}/g, userData.jobTitle)
          .replace(/{workingHours}/g, userData.workingHours)
          .replace(/{directNumber}/g, userData.directPhone); 

          var sigAPIUrl = Utilities.formatString('https://www.googleapis.com/gmail/v1/users/%s/settings/sendAs/%s',userData.email, userData.email);

          var response = UrlFetchApp.fetch(sigAPIUrl, {
            method: "PUT",
            muteHttpExceptions: true,
            contentType: "application/json",
            headers: {
              Authorization: 'Bearer ' + service.getAccessToken()
            },
            payload: JSON.stringify({
              'signature': userSig
            })
          });

          if (response.getResponseCode() !== 200) {
            Logger.log('There was an error: ' + response.getContentText());
          } else {
            Logger.log("Signature updated for "+user.primaryEmail);
          }
        }
      }); 

    } else {
      Logger.log('No users found.');
    }
    pageToken = page.nextPageToken;
  } while (pageToken);
}

function getOAuthService(userId) {
  return OAuth2.createService("Signature Setter "+userId)
  .setTokenUrl('https://accounts.google.com/o/oauth2/token')
  .setPrivateKey(auth.private_key)
  .setIssuer(auth.client_email)
  .setPropertyStore(PropertiesService.getScriptProperties())
  .setSubject(userId)
  .setParam('access_type', 'offline')
  .setScope('https://www.googleapis.com/auth/gmail.settings.basic https://www.googleapis.com/auth/gmail.settings.sharing');
}

可能原因与解决方案

1. 域范围委派权限未同步到新用户

新用户创建后,Google Workspace的域范围委派权限可能未即时同步,导致服务账号无法代表新用户获取有效令牌。

解决步骤:

  • 登录Google Workspace管理控制台,进入「安全」>「API控制」>「域范围委派」
  • 找到对应服务账号的客户端ID,确认已授权范围包含https://www.googleapis.com/auth/gmail.settings.basic和https://www.googleapis.com/auth/gmail.settings.sharing
  • 无需修改内容,直接点击保存按钮触发权限同步
  • 修改getOAuthService函数,强制重置旧令牌缓存:
    function getOAuthService(userId) {
      var service = OAuth2.createService("Signature Setter "+userId)
      .setTokenUrl('https://accounts.google.com/o/oauth2/token')
      .setPrivateKey(auth.private_key)
      .setIssuer(auth.client_email)
      .setPropertyStore(PropertiesService.getScriptProperties())
      .setSubject(userId)
      .setParam('access_type', 'offline')
      .setScope('https://www.googleapis.com/auth/gmail.settings.basic https://www.googleapis.com/auth/gmail.settings.sharing');
      
      // 重置旧令牌,强制获取新令牌
      if (!service.hasAccess()) {
        service.reset();
        service.getAccessToken();
      }
      return service;
    }
    

2. 服务账号密钥或权限异常

重新创建服务账号能暂时解决问题,可能是密钥有效期过短,或服务账号权限被意外修改。

解决步骤:

  • 进入Google Cloud控制台,找到对应服务账号,将密钥设置为长期有效
  • 在Google Workspace管理控制台的「IAM与管理员」中,确保服务账号被授予Gmail Settings Admin角色
  • 避免频繁删除重建服务账号,优先通过刷新权限和令牌缓存解决问题

3. 新用户Gmail账号未初始化

新用户若从未登录过Gmail,账号未完成初始化,会导致API无法访问其设置。

解决步骤:

  • 要求新用户至少登录一次Gmail完成账号初始化
  • 在脚本中添加错误分支,当返回403或权限相关错误时,记录并提示用户完成初始化

4. OAuth2令牌缓存冲突

脚本使用PropertiesService存储令牌,可能存在新用户令牌与旧缓存数据冲突的情况。

解决步骤:

  • 新增清理过期令牌的函数,定期执行:
    function cleanExpiredTokens() {
      var props = PropertiesService.getScriptProperties();
      var keys = props.getKeys();
      keys.forEach(function(key) {
        if (key.startsWith("Signature Setter ")) {
          var tokenData = JSON.parse(props.getProperty(key));
          if (tokenData.expires_at < Date.now()) {
            props.deleteProperty(key);
          }
        }
      });
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 03:43:14