如何在Cloud Function中验证Firebase自定义令牌createCustomToken?
验证Firebase自定义令牌并生成新令牌的实现方案
工具选择:优先结合Firebase Admin SDK与JWT库
Firebase Admin SDK是官方身份管理工具,负责生成新自定义令牌和用户身份管理;验证自定义令牌的签名与解码负载需用到jsonwebtoken第三方库——因为Admin SDK暂无直接验证自定义令牌的API,而JWT库可结合谷歌公钥完成签名验证,两者搭配是最安全且适配的方案。
验证并解码自定义令牌的具体实现
以下是Cloud Function中的完整逻辑示例:
const jwt = require('jsonwebtoken'); const admin = require('firebase-admin'); const fetch = require('node-fetch'); // 缓存谷歌公钥,避免重复请求 let cachedKeys = null; let cacheExpiry = 0; // 获取谷歌公钥(用于验证自定义令牌签名) async function getGooglePublicKeys() { const now = Date.now(); // 缓存有效期1小时 if (cachedKeys && now < cacheExpiry) { return cachedKeys; } const response = await fetch('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'); cachedKeys = await response.json(); cacheExpiry = now + 3600000; return cachedKeys; } exports.refreshCustomToken = async (req, res) => { const oldToken = req.body.token; if (!oldToken) { return res.status(400).send({ message: '缺少令牌参数' }); } try { // 解码令牌头部获取公钥ID(kid),不验证签名 const decodedToken = jwt.decode(oldToken, { complete: true }); if (!decodedToken || !decodedToken.header || !decodedToken.header.kid) { throw new Error('无效的令牌格式'); } // 获取对应公钥 const publicKeys = await getGooglePublicKeys(); const publicKey = publicKeys[decodedToken.header.kid]; if (!publicKey) { throw new Error('无法获取验证公钥'); } // 验证令牌的签名、有效期及关键字段 const payload = jwt.verify(oldToken, publicKey, { audience: 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit', issuer: `${admin.app().options.credential.projectId}@appspot.gserviceaccount.com` }); // 提取用户UID与自定义声明(移除JWT标准字段) const uid = payload.uid; const customClaims = { ...payload }; ['iss', 'sub', 'aud', 'iat', 'exp', 'uid'].forEach(key => delete customClaims[key]); // 生成新的自定义令牌 const newToken = await admin.auth().createCustomToken(uid, customClaims); // 发送新令牌至用户邮箱(替换为你的邮件发送逻辑) await sendUserLoginEmail(payload.email, newToken); res.status(200).send({ message: '您的链接已过期,新链接已发送至邮箱' }); } catch (error) { if (error.name === 'TokenExpiredError') { res.status(400).send({ message: '您的链接已过期,新链接已发送至邮箱' }); } else { res.status(401).send({ message: '无效的令牌,请重新请求登录链接' }); } } }; // 示例邮件发送函数(需替换为实际实现) async function sendUserLoginEmail(email, token) { // 此处集成你的邮件服务(如SendGrid、Firebase Extensions邮件功能) // 生成包含token的登录链接并发送 }
安全验证与最佳实践
- 严格验证令牌字段:除签名和过期时间外,必须验证
aud(固定为上述谷歌API地址)和iss(你的项目服务账号邮箱),防止伪造令牌。 - 缓存公钥:谷歌公钥更新频率低,缓存1小时可减少请求次数,提升性能。
- 限制令牌有效期:自定义令牌设置较短过期时间(如15分钟),降低泄露风险。
- 防止重复刷新:可在Firebase Firestore中记录用户最近一次发送令牌的时间,限制10分钟内无法重复请求,避免邮件轰炸。
- 日志记录:记录每次令牌刷新操作的用户UID、时间、旧令牌状态,便于后续排查安全问题。
- 权限控制:Cloud Function需设置合适的访问权限,仅允许合法客户端调用(如通过App Check验证请求来源)。
内容的提问来源于stack exchange,提问作者Thomas
相关产品推荐
相关产品推荐

