如何为Firebase Phone Auth实现移动端设备级速率限制?
Firebase Phone Auth 设备级速率限制实现方案
结合你已经完成的Play Integrity应用验证,以下是实现单设备每小时最多5条短信请求限制的具体步骤:
1. 选择可靠的设备标识
要实现设备级限制,首先得拿到稳定且不易伪造的设备标识:
- 优先用Firebase Installations ID:通过
FirebaseInstallations.getInstance().getId()获取,这是与应用绑定的唯一标识,卸载重装会重置,安全性更高,且无需额外权限。 - 备选Android ID:通过
Settings.Secure.ANDROID_ID获取,设备重置或刷机后会变更,但无需特殊权限,适合需要跨应用保持标识的场景。 - 禁止使用IMEI、MAC地址:前者在Android 10+需要敏感权限,后者已被系统屏蔽获取,且易被伪造。
2. 后端存储速率限制数据
用Firebase的Firestore或实时数据库存储设备的请求计数和时间窗口信息,推荐Firestore:
- 创建集合
phoneAuthRateLimits,每个文档的ID使用哈希后的设备标识(比如SHA-256哈希,避免明文存储用户设备信息)。 - 文档包含两个字段:
requestCount: 当前时间窗口内的请求次数windowStart: 时间窗口的起始时间戳(单位毫秒)
3. 集成速率限制到短信请求流程
必须将限制逻辑放在后端(比如Firebase Cloud Functions),前端仅负责收集设备标识和调用后端接口,防止被绕过:
- 前端收集设备标识,同时获取Play Integrity验证令牌,一起发送到自定义云函数。
- 云函数先验证Play Integrity令牌(确保请求来自授权应用)。
- 云函数检查该设备的速率限制数据:
- 如果设备无记录,或当前时间已超出窗口(距离
windowStart超过1小时):重置计数为1,更新窗口起始时间为当前时间。 - 如果在窗口内且请求次数≥5:返回错误,拒绝发起短信请求。
- 如果在窗口内且次数<5:计数+1,更新数据库,允许发起短信请求。
- 如果设备无记录,或当前时间已超出窗口(距离
- 前端收到允许结果后,再调用Firebase的
signInWithPhoneNumber方法发送验证码。
4. 云函数示例(Node.js)
const functions = require("firebase-functions"); const admin = require("firebase-admin"); const crypto = require("crypto"); admin.initializeApp(); // 验证Play Integrity的方法(你已实现的逻辑) async function verifyPlayIntegrity(token) { // 这里替换成你的Play Integrity验证逻辑 return true; } exports.checkPhoneAuthRateLimit = functions.https.onCall(async (data, context) => { // 1. 验证应用合法性 const isValid = await verifyPlayIntegrity(data.integrityToken); if (!isValid) { throw new functions.https.HttpsError("permission-denied", "请求来源未授权"); } // 2. 处理设备标识(哈希后存储) const rawDeviceId = data.deviceId; const hashedDeviceId = crypto.createHash("sha256").update(rawDeviceId).digest("hex"); const windowDuration = 60 * 60 * 1000; // 1小时毫秒数 const maxRequests = 5; const rateLimitRef = admin.firestore().collection("phoneAuthRateLimits").doc(hashedDeviceId); const docSnapshot = await rateLimitRef.get(); let requestCount = 1; let windowStart = Date.now(); if (docSnapshot.exists) { const docData = docSnapshot.data(); const currentTime = Date.now(); // 检查是否在当前时间窗口内 if (currentTime - docData.windowStart < windowDuration) { if (docData.requestCount >= maxRequests) { throw new functions.https.HttpsError("resource-exhausted", "请求过于频繁,请1小时后再尝试"); } requestCount = docData.requestCount + 1; windowStart = docData.windowStart; } // 超出窗口则自动重置,requestCount=1,windowStart=当前时间 } // 3. 更新速率限制数据 await rateLimitRef.set({ requestCount, windowStart }); // 4. 返回允许请求的结果 return { allowed: true, message: "可以发起短信验证请求" }; });
5. 额外优化建议
- 定时清理过期数据:用Firebase定时云函数,删除
windowStart超过24小时的文档,减少数据库存储占用。 - 双重限制策略:可以同时限制同一手机号+设备的请求次数,进一步防止恶意请求。
- 错误提示优化:给用户返回明确的冷却时间提示,比如“距离下次请求还有XX分钟”。
内容的提问来源于stack exchange,提问作者Pier1 Sys
相关产品推荐
相关产品推荐

