You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

关于为Apps Script项目分配自定义OAuth Client及同GCP下多项目调用Cloud Function权限问题的咨询

为Apps Script项目分配自定义OAuth Client及同GCP下多项目调用Cloud Function权限问题的解决方案

兄弟,你这问题我前阵子刚帮同事解决过!你精准抓住了核心——就是JWT里的aud(受众)声明不一样,导致Cloud Function的IAM验证卡壳了。我给你三个从易到难的解决方案,你按需选:

方案一:最直接——给Cloud Function添加第二个Apps Script的客户端ID权限

这是最快解决问题的方法,不用改任何代码,只要在GCP控制台加个权限:

  • 打开GCP控制台,找到你的Cloud Function,进入「权限」标签页
  • 点击「添加」,在「新主体」里输入clientId:你的Apps Script B的OAuth客户端ID(这个客户端ID在GCP的「API和服务」→「凭据」里能找到,就是B绑定GCP时自动创建的那个OAuth 2.0客户端ID)
  • 角色选择「Cloud Functions → Cloud Functions 调用者」
  • 保存后,等个1-2分钟让权限生效,再用B调用就没问题了

原理:Cloud Function的身份验证会检查令牌的aud值,只要这个aud对应的主体(也就是客户端ID)拥有调用权限,就能通过验证。你之前只给了用户身份权限,但用户用B的令牌调用时,aud是B的客户端ID,这个ID没在权限列表里,所以被拒了。

方案二:复用同一个OAuth客户端给多个Apps Script项目

如果你确实想让A和B用同一个OAuth客户端,也是可以的,步骤稍微多一点:

  1. 先在GCP控制台的「API和服务」→「凭据」里,找到Apps Script A绑定的那个OAuth客户端ID,记下它的客户端ID,并打开它的编辑页面
  2. 在「已授权的重定向URI」里,添加Apps Script B的重定向URI:格式是https://script.google.com/macros/d/[B的项目ID]/usercallback(B的项目ID在Apps Script B的「项目设置」里能找到)
  3. 打开Apps Script B的项目,点击右上角「项目设置」,勾选「显示appsscript.json清单文件」
  4. 编辑appsscript.json,在现有的配置里添加oauthClient字段,比如:
    {
      "timeZone": "Asia/Shanghai",
      "dependencies": {},
      "exceptionLogging": "STACKDRIVER",
      "runtimeVersion": "V8",
      "oauthScopes": [
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/script.external_request"
      ],
      "oauthClient": {
        "clientId": "你刚才记下的A的OAuth客户端ID"
      }
    }
    
  5. 保存后,回到Apps Script B的编辑器,点击「运行」→「重新授权」,跟着流程完成权限同意
  6. 之后B调用Cloud Function时,就会用A的OAuth客户端ID作为aud,自然就能通过验证了

方案三:用服务账号统一调用(最灵活)

如果以后还要加更多Apps Script项目,或者想统一管理调用权限,用服务账号是最优解:

  1. 在GCP控制台创建一个新的服务账号,角色直接选「Cloud Functions 调用者」
  2. 给这个服务账号创建一个密钥(JSON格式),下载到本地
  3. 打开每个Apps Script项目(A和B),点击「项目设置」→「脚本属性」,添加一个键值对:键是SERVICE_ACCOUNT_KEY,值是你下载的JSON密钥的内容(注意不要有换行)
  4. 在Apps Script里编写调用代码,用服务账号的JWT签名请求,示例代码:
    function callMyCloudFunction() {
      // 从脚本属性获取服务账号密钥
      const key = JSON.parse(PropertiesService.getScriptProperties().getProperty('SERVICE_ACCOUNT_KEY'));
      // 生成JWT令牌
      const jwt = generateJwt(key);
      // Cloud Function的URL
      const functionUrl = "https://us-central1-你的项目ID.cloudfunctions.net/你的函数名";
      // 发送请求
      const response = UrlFetchApp.fetch(functionUrl, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${jwt}`,
          "Content-Type": "application/json"
        },
        payload: JSON.stringify({/* 你的请求参数,按需修改 */})
      });
      console.log("调用结果:", response.getContentText());
    }
    
    function generateJwt(serviceAccountKey) {
      const now = Math.floor(Date.now() / 1000);
      const expiry = now + 3600; // 令牌有效期1小时
      // JWT头部
      const header = JSON.stringify({ alg: "RS256", typ: "JWT" });
      // JWT载荷,aud要填Cloud Function的完整URL
      const payload = JSON.stringify({
        iss: serviceAccountKey.client_email,
        sub: serviceAccountKey.client_email,
        aud: "https://us-central1-你的项目ID.cloudfunctions.net/你的函数名",
        exp: expiry,
        iat: now
      });
      // 生成签名
      const signature = Utilities.computeRsaSha256Signature(
        Utilities.base64EncodeWebSafe(header) + "." + Utilities.base64EncodeWebSafe(payload),
        serviceAccountKey.private_key
      );
      // 拼接成完整的JWT
      return Utilities.base64EncodeWebSafe(header) + "." + Utilities.base64EncodeWebSafe(payload) + "." + Utilities.base64EncodeWebSafe(signature);
    }
    
  5. 这种方法的好处是,所有Apps Script项目都用同一个服务账号调用,权限统一管理,不用管每个项目的OAuth客户端ID,后续加新项目只要复制这段代码就行

三个方案里,方案一最快,适合快速解决当前问题;方案二适合想统一OAuth客户端的场景;方案三最灵活,适合长期维护和扩展。你可以根据自己的需求选~

火山引擎 最新活动