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

低流量Firebase Web应用防恶意计费攻击的方案及最佳实践咨询

方案可行性分析

你的方案完全适用于低流量(日均100用户)的场景,各环节互补且成本极低:

  • Firestore安全规则做速率限制:基于用户最后写入时间戳的方式计算量小,不会在低流量下产生性能瓶颈,能直接在写入前拦截大部分批量操作。
  • Cloud Function禁用违规用户:配合规则的拦截逻辑,能从认证层彻底阻止恶意用户的后续操作,低流量下函数触发频率低,不会产生额外的资源压力。
  • 低注册配额:提前拦截批量注册的恶意账号,避免后续的写入攻击,对正常用户的影响可以忽略(普通用户不会短时间注册多个账号)。
自定义速率限制最佳实践

针对无开箱即用方案的情况,结合你的场景,以下是落地的最佳实践:

一、Firestore安全规则实现前置拦截

核心思路是用独立集合记录用户的写入行为,通过原子操作更新并校验时间间隔,避免竞态条件:

  1. 创建user_writes集合,每个用户对应一个文档(ID为用户UID),存储字段:
    • last_write_time:最后一次写入评论的时间戳
    • write_count:指定时间窗口内的写入次数(可选,用于更精细的阈值控制)
  2. 安全规则示例:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // 评论集合的创建权限校验
    match /reviews/{review} {
      allow create: if request.auth != null && 
        // 校验两次写入间隔至少1分钟
        (
          !exists(/databases/$(database)/documents/user_writes/$(request.auth.uid)) ||
          get(/databases/$(database)/documents/user_writes/$(request.auth.uid)).data.last_write_time + duration.value(60000) < request.time
        ) &&
        // 原子更新用户写入记录,确保规则校验和状态更新原子化
        existsAfter(/databases/$(database)/documents/user_writes/$(request.auth.uid), {
          last_write_time: request.time,
          write_count: increment(1)
        });
    }
    // 用户写入记录仅允许本人读写
    match /user_writes/{uid} {
      allow read, write: if request.auth != null && request.auth.uid == uid;
    }
  }
}
  • 注:规则中加入了!exists(...)分支,处理用户首次写入评论时user_writes文档不存在的情况。

二、Cloud Function实现违规用户封禁

通过监听user_writes集合的更新,触发用户禁用逻辑,彻底阻断攻击:

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

exports.blockAbusiveWriters = functions.firestore
  .document('user_writes/{uid}')
  .onUpdate(async (change, context) => {
    const { write_count, last_write_time } = change.after.data();
    // 设定阈值:10分钟内超过5次写入即判定为违规
    const ABUSE_THRESHOLD = 5;
    const TIME_WINDOW = 600000; // 10分钟,单位毫秒

    // 计算当前时间窗口内的写入次数(可选,替代累计计数)
    const now = Date.now();
    const windowStart = now - TIME_WINDOW;
    if (last_write_time.toMillis() >= windowStart && write_count > ABUSE_THRESHOLD) {
      // 禁用违规用户,禁用后用户令牌自动失效
      await admin.auth().updateUser(context.params.uid, { disabled: true });
      // 记录违规日志,便于后续排查
      await admin.firestore().collection('abuse_logs').add({
        uid: context.params.uid,
        timestamp: admin.firestore.FieldValue.serverTimestamp(),
        write_count,
        reason: "excessive_write_attempts"
      });
    }
  });
  • 优化点:可以定时触发Cloud Function,重置所有用户的write_count(比如每天零点),避免用户因历史记录被误封。

三、注册配额的落地细节

除了IP限制,结合邮箱验证进一步提升恶意注册的门槛:

  1. 用Auth触发器实现IP级注册配额:
exports.enforceRegistrationLimit = functions.auth.user().onCreate(async (user) => {
  const registerIp = user.metadata.lastSignInIp;
  const quotaDoc = admin.firestore().collection('registration_quota').doc(registerIp);
  const quotaSnap = await quotaDoc.get();

  let todayCount = 0;
  const now = new Date();
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());

  if (quotaSnap.exists) {
    const { count, last_reset } = quotaSnap.data();
    // 跨天则重置计数
    if (last_reset.toDate() < today) {
      todayCount = 0;
    } else {
      todayCount = count;
    }
  }

  // 限制单IP每日最多3个注册账号
  if (todayCount >= 3) {
    await admin.auth().deleteUser(user.uid);
    throw new functions.https.HttpsError('resource-exhausted', '每日注册数量已达上限');
  } else {
    await quotaDoc.set({
      count: todayCount + 1,
      last_reset: admin.firestore.Timestamp.fromDate(today)
    }, { merge: true });
  }
});
  1. 强制邮箱验证:在Firebase Auth控制台开启“邮箱验证”,只有验证通过的用户才能提交评论,大幅增加恶意注册的成本。

四、补充优化措施

  • 内容审核:接入Firebase Content Moderation API,自动过滤垃圾评论,同时减少无效写入的数量。
  • 监控告警:在Firebase控制台设置监控规则,比如Firestore写入量突增、Cloud Function异常触发,及时收到邮件告警。
  • 规则简化:低流量场景下避免复杂的规则逻辑,比如不要在规则中嵌套多层查询,减少性能开销。
  • 边缘测试:模拟用户快速连续提交评论,验证规则拦截、函数封禁的流程是否正常运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:43:29