通过Google Apps Script调用需ServiceAccount认证的Cloud Function遇401错误求助
已完成的前置配置
- 已为Google Apps Script添加官方最新版(v43)OAuth2库
- Google Apps Script脚本与Cloud Function所属GCP项目关联
- 同一GCP项目内创建了ServiceAccount
- 该ServiceAccount已配置Owner、Logs Writer、Cloud Functions Admin、Cloud Functions Invoker角色
- 已为ServiceAccount生成JSON密钥文件并复制到脚本中
问题现状
调用Cloud Function后始终返回:
401 Error: Unauthorized Your client does not have permission to the requested URL
已尝试的解决步骤
- 允许未认证调用时可正常调用Cloud Function
- 尝试使用OAuth2库的JWT方法,仍返回相同错误
- 使用ChatGPT生成的类似代码,错误依旧
调用代码
const serviceAccount = { "type": "service_account", "project_id": "xxx", "private_key_id": "xxx", "private_key": "xxx", "client_email": "xxx", "client_id": "xxx", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "xxx", "universe_domain": "googleapis.com" }; const createService = (name, serviceAccount, scopes) => { return OAuth2.createService(name) .setTokenUrl('https://accounts.google.com/o/oauth2/token') .setPrivateKey(serviceAccount.private_key) .setIssuer(serviceAccount.client_email) .setPropertyStore(PropertiesService.getScriptProperties()) .setCache(CacheService.getUserCache()) .setLock(LockService.getUserLock()) .setScope(scopes); }; const sendRequest = (url, oauthParams) => { const { serviceName, serviceAccount, scopes } = oauthParams; const oauthService = createService( serviceName, serviceAccount, scopes ); if (!oauthService.hasAccess()) { console.log(oauthService.getLastError()); return; } const headers = { Authorization: `Bearer ${oauthService.getAccessToken()}`, }; const options = { method: 'get', headers, muteHttpExceptions: true, }; return UrlFetchApp.fetch(url, options).getContentText(); }; const callCF = () => { const url = 'xxx'; const oauthParams = { serviceName: 'GWMservice', serviceAccount, scopes: ['https://www.googleapis.com/auth/cloud-platform'], }; console.log(sendRequest(url, oauthParams)); }; callCF();
排查方向与修复方案
修正Token URL
当前createService中使用的https://accounts.google.com/o/oauth2/token不适合ServiceAccount JWT认证,需替换为serviceAccount对象中自带的token_uri(即https://oauth2.googleapis.com/token),确保获取的是服务端专用令牌。指定令牌受众(Audience)
调用Cloud Function时,JWT令牌的受众必须匹配函数的URL。修改createService方法,添加.setAudience(url)绑定目标函数地址:const createService = (name, serviceAccount, scopes, url) => { return OAuth2.createService(name) .setTokenUrl(serviceAccount.token_uri) .setPrivateKey(serviceAccount.private_key) .setIssuer(serviceAccount.client_email) .setPropertyStore(PropertiesService.getScriptProperties()) .setCache(CacheService.getUserCache()) .setLock(LockService.getUserLock()) .setScope(scopes) .setAudience(url); // 绑定函数URL作为受众 };同时在
sendRequest中传递url参数给createService。检查ServiceAccount密钥完整性
确认脚本中private_key字段完整包含-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----,且无换行符丢失或多余空格。清除旧令牌缓存
脚本通过CacheService存储令牌,可能存在过期或错误的旧令牌:- 打开Google Apps Script编辑器→项目设置→脚本属性,删除所有相关属性
- 在代码中临时添加
oauthService.reset()重置服务,清除缓存的旧令牌
验证Cloud Function的IAM绑定
登录GCP控制台,进入目标Cloud Function的「权限」页面,确认ServiceAccount被直接授予roles/cloudfunctions.invoker权限,而非通过组或其他间接方式绑定。
内容的提问来源于stack exchange,提问作者JShinigami

