You need to enable JavaScript to run this app. 使用Firebase Admin Auth时如何验证Google Cloud Functions的HTTPS调用身份
优惠活动
大模型
产品
解决方案
定价
更多

使用Firebase Admin Auth时如何验证Google Cloud Functions的HTTPS调用身份

鉴权实现方案

你现有鉴权逻辑为验证Firebase ID令牌的Bearer请求头,针对Google Cloud Functions(以下简称GCF)的调用鉴权需求,有两种低改造成本的实现方案:


方案1:复用现有Firebase鉴权逻辑,无需修改服务端代码

该方案仅需调整GCF侧代码,适合服务端不方便发版迭代的场景。

操作步骤

  1. 给GCF绑定有权限访问Firebase资源的服务账号(默认GCF的运行时服务账号已拥有对应权限,无需额外配置)
  2. 在GCF中初始化Firebase Admin SDK,生成专属自定义令牌后换取标准Firebase ID令牌,缓存复用避免频繁生成
  3. GCF请求HTTP服务时,在Authorization请求头中带上Bearer <ID令牌>即可直接适配现有鉴权逻辑

GCF侧代码示例:

const admin = require('firebase-admin');
const fetch = require('node-fetch');

// 初始化Firebase Admin,GCF运行时会自动读取环境内的服务账号凭证,无需手动填写密钥
if (admin.apps.length === 0) {
  admin.initializeApp();
}

// 缓存令牌与过期时间,默认令牌有效期1小时,提前5分钟刷新
let cachedIdToken = null;
let tokenExpireAt = 0;

async function getValidIdToken() {
  if (cachedIdToken && Date.now() < tokenExpireAt - 300000) {
    return cachedIdToken;
  }
  // 生成自定义令牌,uid可设置为服务专属标识,方便后续业务逻辑区分调用方
  const customToken = await admin.auth().createCustomToken('cloud-function-internal-service');
  // 调用Firebase Auth接口换取标准ID令牌,此处替换为你自己的Firebase Web API密钥
  const res = await fetch('https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=你的Firebase Web API密钥', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({token: customToken, returnSecureToken: true})
  });
  const authResult = await res.json();
  cachedIdToken = authResult.idToken;
  tokenExpireAt = Date.now() + authResult.expiresIn * 1000;
  return cachedIdToken;
}

// 请求你的HTTP服务示例
async function callYourHttpServer(apiPath) {
  const idToken = await getValidIdToken();
  return fetch(`https://你的HTTP服务地址/${apiPath}`, {
    headers: {
      'Authorization': `Bearer ${idToken}`
    }
  });
}

方案2:扩展服务端鉴权逻辑,原生支持GCF身份验证

该方案仅需给服务端中间件增加GCP OIDC令牌验证分支,GCF侧无需依赖Firebase相关逻辑,性能更高、逻辑更清晰,适合服务端可迭代的场景。

服务端中间件修改示例

const admin = require('firebase-admin');
const {OAuth2Client} = require('google-auth-library');
// 替换为你自己的GCP项目编号,用于验证GCF服务账号归属
const GCP_PROJECT_NUMBER = '你的GCP项目编号';
// 替换为你自己的HTTP服务地址,作为令牌验证的受众
const TARGET_AUDIENCE = 'https://你的HTTP服务地址';
const gcpAuthClient = new OAuth2Client();

const validateRequest = async (req, res, next) => {
  if (
    (!req.headers.authorization || !req.headers.authorization.startsWith("Bearer ")) 
    && !(req.cookies && req.cookies.__session)
  ) {
    res.status(403).send("Unauthorized 1");
    return;
  }

  let token;
  if (req.headers.authorization?.startsWith("Bearer ")) {
    token = req.headers.authorization.split("Bearer ")[1];
  } else if (req.cookies?.__session) {
    token = req.cookies.__session;
  } else {
    res.status(403).send("Unauthorized 2");
    return;
  }

  try {
    // 优先走原有Firebase ID令牌验证逻辑,适配浏览器端请求
    const decodedIdToken = await admin.auth().verifyIdToken(token);
    req.user = decodedIdToken;
    next();
    return;
  } catch (firebaseAuthError) {
    // Firebase验证失败,尝试验证GCP OIDC令牌,适配GCF请求
    try {
      const ticket = await gcpAuthClient.verifyIdToken({
        idToken: token,
        audience: TARGET_AUDIENCE
      });
      const payload = ticket.getPayload();
      // 验证令牌来自你所属的GCP项目服务账号
      if (payload?.iss === 'https://accounts.google.com' && payload?.azp?.startsWith(`${GCP_PROJECT_NUMBER}-`)) {
        req.user = {
          uid: 'cloud-function-internal-service',
          serviceAccount: true,
          email: payload.email
        };
        next();
        return;
      }
    } catch (gcpAuthError) {
      console.error('GCP令牌验证失败:', gcpAuthError);
    }
  }

  res.status(403).send("Unauthorized 3");
  return;
};

GCF侧代码示例

const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
const TARGET_AUDIENCE = 'https://你的HTTP服务地址';

async function getGcpIdToken() {
  const client = await auth.getIdTokenClient(TARGET_AUDIENCE);
  return client.idTokenProvider.fetchIdToken(TARGET_AUDIENCE);
}

// 请求你的HTTP服务示例
async function callYourHttpServer(apiPath) {
  const idToken = await getGcpIdToken();
  return fetch(`https://你的HTTP服务地址/${apiPath}`, {
    headers: {
      'Authorization': `Bearer ${idToken}`
    }
  });
}

内容的提问来源于stack exchange,提问作者Hyper Heisenberg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 20:18:02