具体的可参考作者这篇文章:
后台添加登录次数限制
这里只做补充说明。
理想情况:
1、第一次登陆正确,直接进入。
2、第一次失败,第二次正确,直接进入。
3、失败次数达到之后,提示时间。等到时间到达之后,复现上面一二步。
显然,原来的并不满足需求,存在bug。具体的可自行验证。
以下是优化,前面内容可直接参考作者文章。
操作:
最后增加对时间的判断。
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public String login(String username, String password, String code, String uuid) {
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String captcha = redisCache.getCacheObject(verifyKey);
redisCache.deleteObject(verifyKey);
if (captcha == null) {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
throw new CaptchaException();
}
// 用户验证
Authentication authentication = null;
try {
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
// authentication = authenticationManager
// .authenticate(new UsernamePasswordAuthenticationToken(username, password));
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, RsaUtils.decryptByPrivateKey(password)));
} catch (Exception e) {
if (e instanceof BadCredentialsException) {
//登录限制
loginError(userService.selectUserByUserName(username));
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
} else {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new CustomException(e.getMessage());
}
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUser());
//判断时间
judgeTime(userService.selectUserByUserName(username));
// 生成token
return tokenService.createToken(loginUser);
}
对loginError方法的优化:
/**
* 登陆错误限制
* 更新时sys_user加两字段
* error_nums int类型(默认0!!)
* error_times varchar类型
*
* @param sysUser
*/
public void loginError(SysUser sysUser) {
long timeNow = timeToStampSecond(DateUtils.getTime());
// if (StrUtil.isNotBlank(sysUser.getErrorTimes())) {
// long errorTimes = timeToStampSecond(sysUser.getErrorTimes()) + UserStatus.WRONG_DURATION * 60;
// if (errorTimes - timeNow <= 0) {
// userService.cleanErrorNums(sysUser.getUserName());
// sysUser.setErrorNums(0);
// }else {
//
// }
// }
//能进来代表用户正常 单纯密码错误
if (sysUser.getErrorNums() > UserStatus.WRONG_TIMES) {
long errorTimes = timeToStampSecond(sysUser.getErrorTimes()) + UserStatus.WRONG_DURATION * 60;
if (errorTimes - timeNow > 0) {
String s = formatHMS(errorTimes - timeNow);
throw new CustomException("对不起,您的账号:" + sysUser.getUserName() + " 错误次数过多,请" + s + "后重试");
} else {
userService.cleanErrorNums(sysUser.getUserName());
sysUser.setErrorNums(0);
}
}
/**如果想添加某时间段内错误某次后限制
在第一次密码错误时添加错误时间,再次密码错误时,对比和第一次密码错误的时间
如在限制时间内 错误次数+1(第一次的错误时间不要覆盖)
如超过自定义的时间则清除错误时间和错误次数
*/
if (sysUser.getErrorNums() < UserStatus.WRONG_TIMES) {
userService.updateErrorNums(sysUser);
throw new CustomException("还有" + (UserStatus.WRONG_TIMES - sysUser.getErrorNums()) + "次输入机会");
} else {
sysUser.setErrorTimes(DateUtils.getTime());
userService.updateErrorNums(sysUser);
throw new CustomException("对不起,您的账号:" + sysUser.getUserName() + " 错误次数过多,请" + UserStatus.WRONG_DURATION + "分钟后重试");
}
}
增加judgeTime方法。
private void judgeTime(SysUser sysUser) {
if (StrUtil.isNotBlank(sysUser.getErrorTimes())) {
long timeNow = timeToStampSecond(DateUtils.getTime());
long errorTimes = timeToStampSecond(sysUser.getErrorTimes()) + UserStatus.WRONG_DURATION * 60;
if (errorTimes - timeNow > 0) {
String s = formatHMS(errorTimes - timeNow);
throw new CustomException("对不起,您的账号:" + sysUser.getUserName() + " 错误次数过多,请" + s + "后重试");
} else {
//清除次数
userService.cleanErrorNums(sysUser.getUserName());
}
}
}