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

如何基于express-rate-limit实现阶梯式递增的登录失败锁定机制?

如何用express-rate-limit实现阶梯式登录失败锁定策略?

我希望实现以下阶梯式登录失败锁定策略:

  • 用户连续10次登录失败后,账号将被锁定10分钟;
  • 锁定解除后,若用户再次尝试登录又出现5次失败,锁定时长增加至20分钟;
  • 20分钟锁定解除后,若该用户仍有3次登录失败,则锁定时长延长至1小时;
  • 最后,1小时锁定解除后,若该用户再次登录失败,账号将被锁定24小时。

我目前使用express-rate-limit作为中间件,现有代码如下:

const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
  windowMs: 10 * 60 * 1000,
  headers: true,
  max: 10,
});
module.exports = apiLimiter;

当前仅第一次的10次失败锁定10分钟的规则生效,请问如何实现后续的阶梯式递增锁定逻辑?


要实现这种阶梯式的锁定策略,express-rate-limit的单实例配置没法直接满足需求,毕竟它默认只处理单一的窗口和次数限制。我们得结合用户状态跟踪和动态调整限制规则来实现阶梯逻辑——核心思路就是用存储(store)记录用户当前的失败阶段和尝试次数,再根据阶段应用对应的锁定规则。

具体实现步骤

  1. 先把所有阶梯阶段的配置定义清楚,明确每个阶段的最大尝试次数和锁定时长;
  2. 自定义keyGenerator来唯一标识每个用户(比如用登录请求里的用户名,或者IP,根据你的实际场景选);
  3. 利用express-rate-limit的存储(默认是MemoryStore,生产环境建议换RedisStore)来保存用户的阶段信息和失败次数;
  4. 在限制器的handler里动态判断用户当前处于哪个阶段,更新尝试次数,再应用对应的锁定规则。

完整代码示例

const rateLimit = require('express-rate-limit');

// 定义阶梯阶段配置:每个阶段的最大尝试次数+锁定时长(毫秒)
const lockStages = [
  { maxAttempts: 10, lockTime: 10 * 60 * 1000 },    // 阶段1:10次失败锁10分钟
  { maxAttempts: 5, lockTime: 20 * 60 * 1000 },    // 阶段2:5次失败锁20分钟
  { maxAttempts: 3, lockTime: 60 * 60 * 1000 },    // 阶段3:3次失败锁1小时
  { maxAttempts: 1, lockTime: 24 * 60 * 60 * 1000 } // 阶段4:1次失败锁24小时
];

const loginLimiter = rateLimit({
  // 自定义用户标识:这里假设登录接口传了username,没有的话可以用req.ip
  keyGenerator: (req) => req.body.username || req.ip,
  headers: true,
  // 初始窗口先设为最小的锁定时长,后续会动态调整
  windowMs: lockStages[0].lockTime,
  // max先设一个大值,后续在handler里动态判断实际限制次数
  max: 999,
  handler: async (req, res, next, options) => {
    const key = options.keyGenerator(req);
    const store = options.store;

    // 从存储中获取用户当前状态:{ 当前阶段, 已尝试次数 }
    let userState = await store.get(key);
    if (!userState) {
      // 用户首次登录失败,初始化状态
      userState = { currentStage: 0, attempts: 1 };
    } else {
      // MemoryStore存的是字符串,需要转成JSON对象
      userState = JSON.parse(userState);
      userState.attempts += 1;
    }

    const currentStageConfig = lockStages[userState.currentStage];

    // 判断当前阶段的尝试次数是否达到上限
    if (userState.attempts >= currentStageConfig.maxAttempts) {
      // 达到上限,锁定用户,同时更新阶段(最多到最后一个阶段)
      const nextStage = Math.min(userState.currentStage + 1, lockStages.length - 1);
      await store.set(key, JSON.stringify({
        currentStage: nextStage,
        attempts: 0 // 锁定后重置尝试次数
      }), currentStageConfig.lockTime);

      // 返回锁定提示
      return res.status(429).json({
        message: `账号已锁定,请${Math.floor(currentStageConfig.lockTime / 60000)}分钟后重试`
      });
    } else {
      // 未达到上限,更新尝试次数,窗口时长设为当前阶段的锁定时长
      await store.set(key, JSON.stringify(userState), currentStageConfig.lockTime);
      next();
    }
  }
});

module.exports = loginLimiter;

几个注意点

  • 用户标识选择:示例用req.body.username作为唯一标识,如果你是无用户名的场景,也可以用req.ip,但IP可能存在共享情况,结合用户身份标识会更准确;
  • 生产环境存储:默认的MemoryStore在服务器重启后会丢失数据,生产环境建议用rate-limit-redis对接Redis,保证状态持久化,同时支持分布式环境;
  • 自动解锁逻辑:锁定时间到期后,存储会自动清除用户的状态,用户下次尝试时会重新从第一阶段开始计数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 17:42:31