关于为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客户端,也是可以的,步骤稍微多一点:
- 先在GCP控制台的「API和服务」→「凭据」里,找到Apps Script A绑定的那个OAuth客户端ID,记下它的客户端ID,并打开它的编辑页面
- 在「已授权的重定向URI」里,添加Apps Script B的重定向URI:格式是
https://script.google.com/macros/d/[B的项目ID]/usercallback(B的项目ID在Apps Script B的「项目设置」里能找到) - 打开Apps Script B的项目,点击右上角「项目设置」,勾选「显示appsscript.json清单文件」
- 编辑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" } } - 保存后,回到Apps Script B的编辑器,点击「运行」→「重新授权」,跟着流程完成权限同意
- 之后B调用Cloud Function时,就会用A的OAuth客户端ID作为
aud,自然就能通过验证了
方案三:用服务账号统一调用(最灵活)
如果以后还要加更多Apps Script项目,或者想统一管理调用权限,用服务账号是最优解:
- 在GCP控制台创建一个新的服务账号,角色直接选「Cloud Functions 调用者」
- 给这个服务账号创建一个密钥(JSON格式),下载到本地
- 打开每个Apps Script项目(A和B),点击「项目设置」→「脚本属性」,添加一个键值对:键是
SERVICE_ACCOUNT_KEY,值是你下载的JSON密钥的内容(注意不要有换行) - 在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); } - 这种方法的好处是,所有Apps Script项目都用同一个服务账号调用,权限统一管理,不用管每个项目的OAuth客户端ID,后续加新项目只要复制这段代码就行
三个方案里,方案一最快,适合快速解决当前问题;方案二适合想统一OAuth客户端的场景;方案三最灵活,适合长期维护和扩展。你可以根据自己的需求选~




