Redis怎么限制用户登录那些事儿,操作步骤和注意点全讲清楚
- 问答
- 2025-12-24 09:24:03
- 2
关于Redis怎么限制用户登录这件事,说白了就是防止有人恶意尝试密码,比如不停地用不同密码登录你的账号,我们可以用Redis这个速度快得像闪电的内存数据库,来记录用户的失败尝试,一旦超过我们设定的安全线,就暂时把这个用户“关一会儿禁闭”,下面我把常见的几种方法和操作步骤、注意点都讲清楚。
最简单的招数:记录失败次数,直接封禁
这个方法最容易理解,核心思想是:用户每输错一次密码,就在Redis里给他记上一笔,当错误次数达到上限,比如5次,就在一段时间内禁止他再次登录。
操作步骤:

- 设计Key: 在Redis里,我们需要一个唯一的钥匙(Key)来标记每个用户,通常我们会用像
login_failures:username这样的格式,username就是具体的用户名,这样每个用户的失败记录都是分开的。 - 记录失败:
- 当用户登录失败时,我们执行一个Redis命令,参考开源项目
spring-security的登录逻辑思想,我们会先检查这个Key是否存在。 - 如果不存在,说明是第一次失败,我们就设置这个Key,并给它一个过期时间(比如10分钟),命令大概是:
SET login_failures:zhangsan 1 EX 600,这表示给用户“zhangsan”记1次失败,并且这个记录10分钟(600秒)后自动消失。 - 如果已经存在,我们就把数字加1,命令是:
INCR login_failures:zhangsan,这个命令会把值增加1。
- 当用户登录失败时,我们执行一个Redis命令,参考开源项目
- 检查是否封禁:
- 在用户每次尝试登录前,我们先通过
GET login_failures:zhangsan命令获取当前的失败次数。 - 如果这个数字大于等于5(或者你设定的其他数字),就直接拒绝登录,告诉他“尝试次数过多,请稍后再试”。
- 在用户每次尝试登录前,我们先通过
- 登录成功要清零: 这是非常关键的一步!当用户终于输对密码成功登录后,我们必须立刻删除这个失败的记录,命令是:
DEL login_failures:zhangsan,不然用户明明登录成功了,却还在封禁期内,那就太冤了。
注意点:
- 过期时间很重要: 一定要设置过期时间(EX),如果不设置,这个失败记录就会永远留在Redis里,用户就被永久封禁了,设置一个合理的时长,比如10分钟或30分钟,给用户一个“冷却期”,也避免Redis被无用的数据占满。
- 原子性操作: 上面提到的
INCR命令是原子性的,意思是即使在很多用户同时登录的高并发场景下,这个“加1”的操作也不会出错,不会出现少记的情况。 - Key的命名要清晰: 使用像
login_failures:这样的前缀,是为了在Redis里管理起来方便,一看就知道这个Key是干嘛的。
更精细的招数:使用滑动时间窗口限制
第一种方法有个小缺点:它是在一个固定的时间窗口(比如10分钟)内计数,假设用户在9分59秒时失败了4次,第10分钟刚过,记录过期了,他在第10分01秒又可以连续失败5次,这其实还是给了攻击者一些机会。

“滑动时间窗口”就是为了解决这个问题,它保证在任何时刻,向前推一段时间(比如10分钟),内的失败次数都不会超标。
操作步骤(使用Redis的List数据结构):
- 设计Key: 这次我们用List(列表)来存时间戳,Key可以叫
login_attempts:username。 - 记录失败:
- 用户登录失败时,我们获取当前的时间戳(精确到毫秒),然后执行命令
LPUSH login_attempts:zhangsan 1640995200000(假设当前时间戳是这个数),这样就把这次失败的时间戳塞进了列表的头部。
- 用户登录失败时,我们获取当前的时间戳(精确到毫秒),然后执行命令
- 检查与清理:
- 我们执行一个Lua脚本(或者分两步在代码里处理),做以下几件事:
a. 修剪列表: 使用
LTRIM命令,只保留最近10分钟内的记录。LTRIM login_attempts:zhangsan 0 4,这个命令本身是按索引修剪,我们需要先计算10分钟前的时间戳,然后移除所有比这个时间戳旧的数据,更常见的做法是,用循环检查列表尾部(最老的记录)的时间戳,如果已经超过10分钟,就弹出它,直到所有记录都在10分钟内为止,这个过程可以用Lua脚本保证原子性。 b. 检查数量: 修剪后,使用LLEN命令获取列表当前的长度,这个长度就是10分钟内的失败次数。 c. 判断是否封禁: 如果长度大于等于5,就拒绝登录。
- 我们执行一个Lua脚本(或者分两步在代码里处理),做以下几件事:
a. 修剪列表: 使用
- 设置列表过期时间: 我们可以给整个List Key设置一个过期时间,比如11分钟,比窗口时间长一点,用于自动清理长时间不活跃用户的数据,节省内存。
注意点:

- 复杂度更高: 这种方法比第一种要复杂,尤其是修剪旧时间戳的逻辑,为了保证效率和高并发下的正确性,强烈建议使用Redis的Lua脚本将检查、修剪、计数几个操作原子性地完成。
- 性能考虑: 如果某个攻击者持续攻击一个账号,这个List会不断被修剪,长度不会无限增长,所以对Redis性能影响可控,但对于正常用户,这个List大部分时间是空的。
- 精度权衡: 时间窗口的精度(是精确到秒还是毫秒)可以根据安全要求调整,精度越高,计算量可能略大。
通用的招数:直接用Redis实现令牌桶
这个方法更常用来限制API调用频率,但用在登录限制上也完全没问题,而且非常灵活,想象每个用户都有一个桶,这个桶以固定的速率掉落“令牌”(代表一次尝试机会),桶有容量上限。
操作步骤:
- 设计Key: 我们需要两个Key来记录一个用户的状态:一个存当前桶里的令牌数量(如
token_bucket:zhangsan),一个存上次补充令牌的时间戳(如last_refill:zhangsan)。 - 尝试登录(消费令牌):
- 当用户尝试登录时,我们通过一个Lua脚本计算: a. 计算应补充的令牌数: 用当前时间减去上次补充时间,再乘以令牌掉落速率(比如每2秒掉1个令牌),算出这段时间应该补多少令牌,但补充后不能超过桶的容量(比如5个)。 b. 检查令牌是否足够: 当前的令牌数加上新补充的令牌数,如果大于等于1(因为登录一次消耗1个令牌),就允许本次登录,并扣减1个令牌,更新上次补充时间为当前时间。 c. 不足则拒绝: 如果令牌不足,就拒绝登录,并告诉他需要等待多久(根据速率算一下)。
- 成功登录怎么办: 在这种模型下,无论登录成功还是失败,都会消耗令牌,所以不需要像第一种方法那样在成功时做特殊处理,它的限制是针对“登录行为”的频率,而不是“失败”的频率。
注意点:
- 灵活性高: 令牌桶算法可以平滑地处理突发请求(因为桶有容量),又能限制长期平均速率,非常强大。
- 实现最复杂: 它需要维护两个Key,并且计算逻辑相对复杂,必须用Lua脚本来保证原子性,否则在并发下会出问题。
- 适用于更广场景: 学会了这个,你不仅可以限制登录,还可以用来限制短信验证码发送、API调用等任何需要限流的场景。
- 小网站或要求不高的场景,用第一种 “计数封禁” 方法,简单有效,够用了。
- 对安全有更高要求,怕被钻时间窗口的空子,就用第二种 “滑动时间窗口”。
- 需要非常灵活的频率控制,或者想一劳永逸学会一个通用限流方案,就花点功夫研究第三种 “令牌桶”。
无论用哪种方法,都要记住几个共同的注意点:Key的命名要有意义、一定要设置过期时间防止内存泄漏、在高并发下使用原子操作(INCR或Lua脚本) 保证数据准确,别忘了在用户成功登录后,根据你选择的方案做好清理工作。
本文由畅苗于2025-12-24发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/67464.html
