如何基于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)记录用户当前的失败阶段和尝试次数,再根据阶段应用对应的锁定规则。
具体实现步骤
- 先把所有阶梯阶段的配置定义清楚,明确每个阶段的最大尝试次数和锁定时长;
- 自定义
keyGenerator来唯一标识每个用户(比如用登录请求里的用户名,或者IP,根据你的实际场景选); - 利用
express-rate-limit的存储(默认是MemoryStore,生产环境建议换RedisStore)来保存用户的阶段信息和失败次数; - 在限制器的
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
相关产品推荐
相关产品推荐

