前言
为防止暴力破解密码,保护用户数据隐私,在输入密码时我们一般都会限制用户尝试密码次数,当多次输错密码后,将在一段时间内锁定账号。越是敏感隐私的数据这一步就越是不可少。像手机银行这种,一般只要输错3次将会锁定24小时,24小时后才能重新尝试。不同的业务系统需要根据颗粒程度和业务需求设置密码尝试次数和锁定时间。
首先先来确定锁定的是具体的用户还是IP地址,一般来说是将尝试登录的人的IP地址锁定,为什么呢?因为某些恶意用户会尝试登录其他用户的账号,如果将具体的用户锁定,那么可能就会误伤正常用户,如果恶意用户拿到账号把系统全部人员都锁定,那正常用户就都没法使用了。但就算锁定IP地址,有些黑客还可以通过IP代理池不停切换IP来尝试密码,所以我们会要求用户密码的复杂度并且在密码输入正确后,还需要验证图形验证码,不过本文还是来主要讲讲怎么限制用户密码错误次数。
使用Redis实现
Redis记录输错密码次数,key为用户IP地址,value为密码错误次数,并且要设置过期时间TTL,过期时间即为锁定时间,当到达阈值后只有Key过期了,才能继续尝试登录。以下密码错误阈值我设置的5次,过期时间为10分钟。
- 用户登录,判断Redis中是否有对应用户IP地址的Key。
- 如果没有对应Key,且账号密码输入正确则返回“登录成功”。如果账号密码输入错误,则添加对应Key和过期时间。
- 如果有对应Key,那我们看对应Key的值是否小于5。
- 如果小于5,表示错误次数还不足5次,此时如果密码正确,则删除对应Key,返回“登录成功”。输入错误则返回“账号密码有误,请重试”,并将Key的值加自增1,并设置过期时间为10分钟。这里需要使用Redis的GET,INCR,EXPIRE命令,建议使用Redis的Lua脚本,保证操作的原子性。
命令:EVAL script numkeys [key1…]
作用:执行script中的脚本,numKeys为传递键个数,后面跟传递的键
EVAL "
local key = KEYS[1]
if redis.call('GET',key) == '5'
then return redis.call('TTL',key);
else
redis.call('INCR',key)
redis.call('EXPIRE',key,600)
return 0;
end" 1 127.0.0.1
- 如果Key的值等于5,表示错误次数已达上限,返回“账号已锁定,请于xx分钟后重试”。
流程图
使用数据库实现
使用数据库的实现比较简单,实现具体用户的锁定。
在数据库用户表中添加两个字段:
🌟 num: 用户密码错误次数
🌟 unlock_time: 解开锁定的时间
- 用户登录,先判断解开锁定时间unlock_time是否比当前时间大。
- 如果比当前时间大,表示已经锁定账号,返回“账号已锁定,请于xx分钟后重试”,如果比当前时间小,再来看错误次数num。
- 如果错误次数num等于5次了,更新unlock_time,返回“账号已锁定,请于xx分钟后重试”。
- 如果错误次数还没有错误5次,就看账号密码是否正确,如果正确了,重置num,返回“登录成功”;没有正确,num+1,返回“账号密码有误,请重试”。
流程图
总结
多次输错密码后限制用户在规定时间内禁止再次登录的功能在以下场景中也是类似的实现:
- 短信验证码发送限制:例如,为了防止恶意请求,对同一手机号码在一定时间内发送短信验证码的次数进行限制。
- 网络攻击防护中的 IP 封禁:当某个 IP 地址在短时间内发起大量异常请求时,会对该 IP 进行封禁一段时间,以防止攻击。
- 账号注册的频率限制:防止恶意批量注册账号,对在一定时间内注册账号的次数进行限制。
- 在线投票的限制:为保证投票公正性,限制同一用户在一定时间内的投票次数。
这些功能都旨在通过限制一定时间内的操作次数或频率,来保障系统的安全、稳定和公平性。