很多地方都要用到重试次数限制,不然就会被暴力破解。比如登录密码。
下面不是完整代码,只是伪代码,提供一个思路。
第一种(先声明,这样写有个bug)
import java.text.MessageFormat; public class Demo { /** * 限制次数 */ private static final Integer MAX_TIMES = 5; /** * 锁定时间(也是key的失效时间) */ private static final Integer LIMIT_TIME = 3; /** * key */ private static final String LIMIT_TIMES_KEY = "LimitTimesKey:%s"; public void deal(String phone, String password){ // 用户id Long userId = 6815356L; // 组装key String key = MessageFormat.format(LIMIT_TIMES_KEY, userId); // 先获取key对应的value String s = redisService.get(key); int currentTimes = s != null ? Integer.parseInt(s) : 0; // 如果当前次数为[LIMIT_TIMES]次或以上,则抛异常 if(currentTimes >= MAX_TIMES){ throw new RuntimeException("请在"+ LIMIT_TIME +"分钟后继续尝试"); } // TODO 做其它逻辑,比如登录操作 Integer code = userService.login(phone, password); // 比如密码不正确的状态码是10086 if(code == 10086){ // 失败次数+1 int thisTimes = currentTimes + 1; String value = String.valueOf(thisTimes); // 如果小于最大限制 if(thisTimes < MAX_TIMES){ redisService.set(key, value); throw new RuntimeException("原密码错误,还可以重试"+ (MAX_TIMES - thisTimes) +"次"); }else{ redisService.setex(key, LIMIT_TIME*60, value); throw new RuntimeException("原密码错误,请在"+ LIMIT_TIME +"分钟后继续尝试"); } } // 登陆成功,清理redis redisService.del(key); } }
以上代码思路:
以上代码有什么问题呢:当失败次数小于最大限制,那里直接set了一个值,没有设置失效时间。如果用户失败了一次就不再尝试了,那么我们设置的key就会永远存在;同时用户在n年后再去登陆,他拥有的重试次数是凌驾于n年前的重试次数之上的,也就是说我今年浪费了1次重试次数,还剩下4次,我明年再重试,我能够重试的次数就不是5而是4了,因为我的重试次数记录一直存在。
import java.text.MessageFormat; public class Demo { /** * 限制次数 */ private static final Integer MAX_TIMES = 5; /** * 锁定时间(也是key的失效时间) */ private static final Integer LIMIT_TIME = 3; /** * key */ private static final String LIMIT_TIMES_KEY = "LimitTimesKey:%s"; public void deal(String phone, String password){ // 用户id Long userId = 6815356L; // 组装key String key = MessageFormat.format(LIMIT_TIMES_KEY, userId); // 先获取key对应的value String s = redisService.get(key); int currentTimes = s != null ? Integer.parseInt(s) : 0; // 如果当前次数为[LIMIT_TIMES]次或以上,则抛异常 if(currentTimes >= MAX_TIMES){ throw new RuntimeException("请在"+ LIMIT_TIME +"分钟后继续尝试"); } // TODO 做其它逻辑,比如登录操作 Integer code = userService.login(phone, password); // 比如密码不正确的状态码是10086 if(code == 10086){ // 失败次数+1 int thisTimes = currentTimes + 1; String value = String.valueOf(thisTimes); // 设置值,重点是失效时间 redisService.setex(key, LIMIT_TIME*60, value); // 如果小于最大限制 if(thisTimes < MAX_TIMES){ throw new RuntimeException("原密码错误,还可以重试"+ (MAX_TIMES - thisTimes) +"次"); }else{ throw new RuntimeException("原密码错误,请在"+ LIMIT_TIME +"分钟后继续尝试"); } } // 登陆成功,清理redis redisService.del(key); } }
改进之后的思路如下: