使用Firebase Admin Auth时如何验证Google Cloud Functions的HTTPS调用身份
鉴权实现方案
你现有鉴权逻辑为验证Firebase ID令牌的Bearer请求头,针对Google Cloud Functions(以下简称GCF)的调用鉴权需求,有两种低改造成本的实现方案:
方案1:复用现有Firebase鉴权逻辑,无需修改服务端代码
该方案仅需调整GCF侧代码,适合服务端不方便发版迭代的场景。
操作步骤
- 给GCF绑定有权限访问Firebase资源的服务账号(默认GCF的运行时服务账号已拥有对应权限,无需额外配置)
- 在GCF中初始化Firebase Admin SDK,生成专属自定义令牌后换取标准Firebase ID令牌,缓存复用避免频繁生成
- 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
相关产品推荐
相关产品推荐

