调用私有HTTP Cloud Function遇401:bearer小写及签名被移除问题
问题描述
我创建了服务账号account-name@project-id.iam.gserviceaccount.com,给它添加了Cloud Functions Invoker和Logs Writer角色;部署了getRandomInteger云函数后,在权限设置里移除了Cloud Functions Invoker角色下的allUsers,换成了这个服务账号。
之后我通过gcloud CLI用该服务账号的密钥文件登录,用curl调用云函数,同时配置了使用同一服务账号的Cloud Workflows调用该函数,但都返回401未授权错误。云函数里打印的Authorization头显示为bearer X.Y.SIGNATURE_REMOVED_BY_GOOGLE(bearer是小写,签名被Google替换),请问哪里操作错了?
相关代码
1. 云函数代码
exports.getRandomInteger = functions .region(process.env.REGION) .runWith({ memory: "128MB", timeoutSeconds: 60 }) .https.onRequest((req, res) => { return cors(req, res, async () => { try { validateHttpMethod(req, "GET"); const authToken = getIdTokenFromRequest(req); await verifyIdToken(authToken); ... return res.status(200).send(randomInteger.toString()); } catch (err) { if (err instanceof HttpError) { return res.status(err.statusCode).send(err.message); } // An internal server error has occurred functions.logger.error(err); return res.status(500).send(err); } }); });
2. getIdTokenFromRequest工具函数
module.exports = (req) => { const authorizationHeader = req.headers.authorization; const sessionCookie = req.cookies?.__session; // Make sure there is a Bearer token in the Authorization header if (!authorizationHeader?.startsWith("Bearer ") && !sessionCookie) { throw AuthErrors.unauthorized(); } if (authorizationHeader?.startsWith("Bearer ")) { return authorizationHeader.split("Bearer ")[1]; } if (sessionCookie) { return sessionCookie; } throw AuthErrors.unauthorized(); };
3. verifyIdToken工具函数
module.exports = async (token) => { try { const decodedIdToken = await admin.auth().verifyIdToken(token); return decodedIdToken; } catch (err) { throw AuthErrors.unauthorized(); } };
4. curl调用命令
curl -i -H "Authorization: Bearer $(gcloud auth print-identity-token)" \ https://project_location-project_id.cloudfunctions.net/getRandomInteger
5. Cloud Workflows配置
main: params: [] steps: - getRandomInteger: call: http.get args: url: https://project_location-project_id.cloudfunctions.net/getRandomInteger auth: type: OIDC result: randomInteger - sleep: call: sys.sleep args: seconds: randomInteger - returnOutput: return: OK
问题根源与修复方案
问题根源
- Token验证逻辑不匹配:你使用Firebase Auth的
admin.auth().verifyIdToken()验证服务账号的OIDC Token,这两种Token不属于同一体系——前者仅支持Firebase用户身份,后者是Google Cloud服务账号的身份凭证,直接验证会失败。 - Authorization头大小写判断严格:你的工具函数要求头前缀是大写的
Bearer,但实际请求的头是小写的bearer,导致函数直接判定为无有效Token,抛出未授权错误。
修复步骤
1. 修复Authorization头的大小写判断
修改getIdTokenFromRequest函数,忽略大小写验证前缀:
module.exports = (req) => { const authorizationHeader = req.headers.authorization; const sessionCookie = req.cookies?.__session; // 忽略大小写判断Bearer前缀 const hasValidBearer = authorizationHeader?.toLowerCase().startsWith("bearer "); if (!hasValidBearer && !sessionCookie) { throw AuthErrors.unauthorized(); } if (hasValidBearer) { // 提取Token时兼容大小写前缀 return authorizationHeader.split(/bearer /i)[1]; } if (sessionCookie) { return sessionCookie; } throw AuthErrors.unauthorized(); };
2. 替换为服务账号Token验证逻辑
安装google-auth-library依赖:
npm install google-auth-library
然后修改verifyIdToken函数,使用Google官方库验证服务账号OIDC Token:
const { OAuth2Client } = require('google-auth-library'); const client = new OAuth2Client(); module.exports = async (token) => { try { const ticket = await client.verifyIdToken({ idToken: token, // 受众设置为你的云函数URL audience: 'https://project_location-project_id.cloudfunctions.net/getRandomInteger', // 限制Token发行方为Google官方账号体系 issuer: 'https://accounts.google.com' }); const payload = ticket.getPayload(); // 额外验证:确保请求来自目标服务账号 if (payload.email !== 'account-name@project-id.iam.gserviceaccount.com') { throw AuthErrors.unauthorized(); } return payload; } catch (err) { throw AuthErrors.unauthorized(); } };
3. 确认云函数权限配置
用gcloud命令验证服务账号的权限是否正确配置:
gcloud functions get-iam-policy getRandomInteger --region=project_location
输出中需包含以下条目:
bindings: - members: - serviceAccount:account-name@project-id.iam.gserviceaccount.com role: roles/cloudfunctions.invoker
内容的提问来源于stack exchange,提问作者Raul
相关产品推荐
相关产品推荐

