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

调用私有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

问题根源与修复方案

问题根源

  1. Token验证逻辑不匹配:你使用Firebase Auth的admin.auth().verifyIdToken()验证服务账号的OIDC Token,这两种Token不属于同一体系——前者仅支持Firebase用户身份,后者是Google Cloud服务账号的身份凭证,直接验证会失败。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 19:09:58