业务场景
需求:为了防止枚举攻击,完成安全性测试扫描。需要限制登录失败次数限制,本文做的是累计登录失败五次账号锁定15min。(如果有一次登录成功则会重新计数)
架构设计
代码实现
@Autowired
private StringRedisTemplate redisTemplate;
private static final String STAFF_LOGIN_FAIL_COUNT = "STAFF:LOGIN:FAIL_COUNT:";
private static final String STAFF_LOGIN_LOCK_FLAG = "STAFF:LOGIN:LOCK_FLAG:";
@PostMapping("/login")
public ResponseEx login(@RequestBody LoginParam param) {
if ("LOCK".equals(redisTemplate.opsForValue().get(STAFF_LOGIN_LOCK_FLAG + req.getUserName()))) {
return ResponseEx.createError("当前账号被锁定,请稍后再试");
}
//登录失败
boolean loginFlag = false;
if (!loginFlag) {
redisTemplate.opsForValue().increment(STAFF_LOGIN_FAIL_COUNT + param.getUserName(), 1);
redisTemplate.expire(STAFF_LOGIN_FAIL_COUNT + param.getUserName(), 1, TimeUnit.HOURS);
if (Integer.parseInt(redisTemplate.opsForValue().get(STAFF_LOGIN_FAIL_COUNT + param.getUserName())) >= 5) {
redisTemplate.opsForValue().set(STAFF_LOGIN_LOCK_FLAG + param.getUserName(), "LOCK", 1, TimeUnit.HOURS);
return ResponseEx.createError("当前账号被锁定,请稍后再试");
}
return ResponseEx.createError("账号或密码错误!");
}
redisTemplate.opsForValue().set(STAFF_LOGIN_FAIL_COUNT + param.getUserName(), "0");
}
逻辑抽象。避免类似的业务写重复的代码。
- 判断是否被锁住
- 次数累增
- 清除次数抽象出来。
- 想要过期时间定制化,需要优化。
/**
* @author charles
* @date 2023/5/5 16:56
*/
public class LockUtil {
private static final String LOCK_KEY_PREFIX = "lock";
private static final String COUNT_KEY_PREFIX = "count";
private static final String COLON = ":";
private static final String LOCK_FLAG = "LOCK";
private static final Duration COUNT_EXPIRE_DURATION = Duration.ofHours(1L);
private static final Duration LOCK_EXPIRE_DURATION = Duration.ofMinutes(15L);
public static Boolean isLock(String bizKey) {
StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
if (LOCK_FLAG.equals(redisTemplate.opsForValue().get(LOCK_KEY_PREFIX.concat(COLON).concat(bizKey)))) {
return false;
}
return true;
}
public static Boolean plusCount(String bizKey) {
StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
String countKey = COUNT_KEY_PREFIX.concat(COLON).concat(bizKey);
String lockKey = LOCK_KEY_PREFIX.concat(COLON).concat(bizKey);
redisTemplate.opsForValue().increment(countKey, 1);
redisTemplate.expire(countKey, COUNT_EXPIRE_DURATION);
if (Integer.parseInt(redisTemplate.opsForValue().get(countKey)) >= 5) {
redisTemplate.opsForValue().set(lockKey, LOCK_FLAG, LOCK_EXPIRE_DURATION);
return false;
}
return true;
}
public static void clearCount(String bizKey) {
StringRedisTemplate redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
String lockKey = LOCK_KEY_PREFIX.concat(COLON).concat(bizKey);
redisTemplate.opsForValue().set(lockKey, "0");
}
}