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

已验证OAuth权限的Google Sheets插件抛出UrlFetch权限异常排查

Google Sheets Workspace插件权限异常调试方案

将生产环境的Google Sheets Workspace插件安装到测试/个人表格环境后,频繁出现权限异常:

Exception: You do not have permission to call UrlFetchApp.fetch. Required permissions: https://www.googleapis.com/auth/script.external_request

完成插件安装流程后,或打开带有该插件的表格时,日志中都会出现这个权限错误。需要定位问题:是否是非CUSTOM_FUNCTION的服务器端脚本(带有独立authMode枚举)尝试发起外部请求(此处是向自有API服务器请求并保存API token)导致?或是Google Sheet/App Script的其他限制,需要针对性解决方案?


onOpen触发的Event日志

{
  "range": {"columnEnd":1,"columnStart":1,"rowEnd":1,"rowStart":1},
  "authMode":"LIMITED",
  "source":{},
  "user":{"email":"","nickname":""}
}

(注:当前是LIMITED授权模式,而非NONE模式,但user的email和nickname属性为空)


异常相关的code.gs代码结构

function onInstall(e) {
   Logger.log(`Install event: ${JSON.stringify(e)}`);
   onOpen(e);
}

function onOpen(e){
// 无论授权模式如何,都添加菜单项
  addMenu();

  if(e && e.authMode == ScriptApp.AuthMode.NONE){
    Logger.log(`Auth mode: ${e.authMode}`);
    // 提示用户授权
    ScriptApp.getAuthorizationInfo(AuthMode).getAuthorizationUrl()
  } else {
    // 除NONE外的其他授权模式
    tempKey = Session.getTemporaryActiveUserKey();
    var user = Session.getActiveUser();
    var email = user.getEmail();
    var username = user.getUsername();
    const currentTime = new Date().getTime();

    //... 省略其他代码,核心逻辑是打包信息并向API服务器发起请求获取token
        try{
      // 调用封装了UrlFetchApp请求的函数,设置payload和headers
      var myApp = createMyApplication();

      // 构造要存储到文档属性的JSON
      var userOrg = {organization: myApp.data.organization, user: myApp.data.user};

      // 将响应token保存到文档属性
      documentProperties.setProperty('APP_AUTH_TOKEN', token);
      documentProperties.setProperty('APP_ORG_DETAILS', JSON.stringify(userOrg) );

      // 将token变量赋值为新的'APP_AUTH_TOKEN'属性值
      token = orgAuthDetails().token;          

    }catch(err){
      return "Error configuring application user";
      }
    }
  }
}

appscript.json清单文件

{
  "addOns": {
    "common": {
      "name": "My sheets app",
      "logoUrl": "https://lh3.googleusercontent.com/__PIC_ID__",
      "layoutProperties": {
        "primaryColor": "#B8136C"
      }
    },
    "sheets": {
      "homepageTrigger": {
        "runFunction": "onOpen"
      }
    }
  },
  "timeZone": "America/New_York",
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "userSymbol": "Sheets",
        "serviceId": "sheets",
        "version": "v4"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "oauthScopes": [
    "https://www.googleapis.com/auth/script.container.ui",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/userinfo.profile",
    "openid",
    "https://www.googleapis.com/auth/drive.file",
    "https://www.googleapis.com/auth/spreadsheets.currentonly",
    "https://www.googleapis.com/auth/script.external_request"
  ],
  "runtimeVersion": "V8",
  "urlFetchWhitelist": [
    "https://www.mysamplesite.net/"
  ],
  "webapp": {
    "executeAs": "USER_ACCESSING",
    "access": "ANYONE"
  }
}

调试与解决方案

1. 核心问题定位:LIMITED授权模式的权限限制

onOpen触发时的authMode是LIMITED,该模式下不允许调用需要敏感权限的服务,包括UrlFetchApp.fetch(需要script.external_request权限)、Session.getActiveUser()(需要用户信息权限)等。即使清单中声明了权限,LIMITED模式仍会限制这些操作。你当前的代码逻辑是只要authMode不是NONE就执行敏感操作,这在LIMITED模式下必然触发异常。

2. 针对性修复步骤

  • 调整onOpen逻辑,仅在FULL授权模式下执行敏感操作
    修改onOpen的条件判断,仅当e.authMode === ScriptApp.AuthMode.FULL时,才执行外部请求和用户信息处理:

    function onOpen(e){
      addMenu();
    
      if(e && e.authMode == ScriptApp.AuthMode.NONE){
        Logger.log(`Auth mode: ${e.authMode}`);
        // 不直接生成授权链接,而是通过菜单引导用户主动授权
      } else if(e && e.authMode === ScriptApp.AuthMode.FULL) {
        // 仅在FULL模式下执行敏感操作
        tempKey = Session.getTemporaryActiveUserKey();
        var user = Session.getActiveUser();
        var email = user.getEmail();
        var username = user.getUsername();
        const currentTime = new Date().getTime();
    
        try{
          var myApp = createMyApplication();
          var userOrg = {organization: myApp.data.organization, user: myApp.data.user};
          documentProperties.setProperty('APP_AUTH_TOKEN', token);
          documentProperties.setProperty('APP_ORG_DETAILS', JSON.stringify(userOrg) );
          token = orgAuthDetails().token;          
        }catch(err){
          return "Error configuring application user";
        }
      }
      // 其他模式(如LIMITED)下仅添加菜单,不执行敏感操作
    }
    
  • 通过菜单触发授权与敏感操作
    在addMenu函数中添加"初始化应用"按钮,让用户主动点击触发FULL授权模式的函数,而非在onOpen中自动执行:

    function addMenu() {
      SpreadsheetApp.getUi()
        .createMenu('我的插件')
        .addItem('初始化应用', 'initializeApp')
        .addToUi();
    }
    
    function initializeApp() {
      // 此函数会以FULL授权模式执行,可安全调用UrlFetchApp和Session
      tempKey = Session.getTemporaryActiveUserKey();
      var user = Session.getActiveUser();
      var email = user.getEmail();
      var username = user.getUsername();
      const currentTime = new Date().getTime();
    
      try{
        var myApp = createMyApplication();
        var userOrg = {organization: myApp.data.organization, user: myApp.data.user};
        documentProperties.setProperty('APP_AUTH_TOKEN', token);
        documentProperties.setProperty('APP_ORG_DETAILS', JSON.stringify(userOrg) );
        token = orgAuthDetails().token;          
        SpreadsheetApp.getUi().alert('初始化成功!');
      }catch(err){
        SpreadsheetApp.getUi().alert('初始化失败:' + err.message);
      }
    }
    
  • 关于user.email为空的问题
    LIMITED模式下,即使授权了用户信息权限,Session.getActiveUser().getEmail()也会返回空值,这是Google的设计限制——只有FULL授权模式下才能获取完整的用户信息。

3. 额外验证点

  • 确认urlFetchWhitelist中的API域名完全匹配(包括HTTP/HTTPS、端口等)
  • 检查插件安装后,用户是否完成了完整的授权流程(部分情况下用户可能跳过授权)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 12:05:00