上次添加密文传输后 这次添加了登陆次数限制(后台均可使用此方法和思路)
简单来讲就是 同一用户输入密码错误某次以上 限制登陆一段时间
实现这个功能分为两点
- 记录密码错误次数和错误时间
- 密码正确后清除错误次数
数据库添加字段
在记录用户信息的表中添加两个字段
错误次数 error_nums int类型 (设置默认为0)
错误时间 error_times varchar类型或datetime类型
添加接口和字段
在你的用户信息(SysUser.java)实体类里添加字段 并生成getset方法
/**
* 错误次数
*/
private int errorNums;
/**
* 错误时间
*/
private String errorTimes;
新增两个接口
/**
* 增加密码错误次数
*
* @param sysUser 用户信息
* @return 结果
*/
public int updateErrorNums(SysUser sysUser);
/**
* 清空密码错误次数
*
* @param username 用户名
* @return 结果
*/
public int cleanErrorNums(String username);
我的系统使用的myBatis 所以在xml文件要加上字段 和sql
<resultMap type="SysUser" id="SysUserResult">
...
...
<result property="errorNums" column="error_nums"/>
<result property="errorTimes" column="error_times"/>
...
</resultMap>
<!--is null or LENGTH( trim( 字段 ) 判断是否为空 或无值 -->
<update id="updateErrorNums" parameterType="SysUser">
update sys_user
set error_nums = if(error_nums is null or LENGTH( trim( error_nums )) = 0, 0, error_nums) + 1,
error_times = if(#{errorTimes} is null,now(),#{errorTimes})
where user_name = #{userName}
</update>
<update id="cleanErrorNums" parameterType="String">
update sys_user set error_nums = 0 where user_name = #{userName}
</update>
查询用户角色的sql也需要加上字段
if(error_nums is null or LENGTH( trim( error_nums )) = 0, 0, error_nums) + 1
当error_nums为空 或无值时 赋值为0 加1 如有值直接+1
if(#{errorTimes} is null,now(),#{errorTimes})
当参数errorTimes为null 后无值 传当前时间(yyyy-MM-dd HH:mm:ss)格式
注意下系统服务器的时间是否为北京时间 不是 可以用下面设置更改
--mysql
select NOW();
select sysdate();
select localtime();
show variables like '%time_zone%';
set global time_zone = '+8:00';
或
set global time_zone='Asia/Shanghai'
--linux设置时间
date -s "20220527 18:30:50"
添加登陆限制接口
根据大家反应 如想某时间段内错误几次后,限制登陆 需多判断下错误时间
/**
* 登陆错误限制
* 更新时sys_user加两字段
* error_nums int类型(默认0!!)
* error_times varchar类型
*
* @param sysUser
*/
public void loginError(SysUser sysUser) {
//能进来代表用户正常 单纯密码错误
if (sysUser.getErrorNums() > UserStatus.WRONG_TIMES) {
long timeNow = DateUtils.timeToStampSecond(DateUtils.getTime());
long errorTimes = DateUtils.timeToStampSecond(sysUser.getErrorTimes()) + UserStatus.WRONG_DURATION * 60;
if (errorTimes - timeNow > 0) {
String s = DateUtils.formatHMS(errorTimes - timeNow);
throw new CustomException("对不起,您的账号:" + sysUser.getUserName() + " 错误次数过多,请" + s + "后重试");
}
}
/**如果想添加某时间段内错误某次后限制
在第一次密码错误时添加错误时间,再次密码错误时,对比和第一次密码错误的时间
如在限制时间内 错误次数+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 + "分钟后重试");
}
}
timeToStampSecond()是string类型日期转时间戳(秒级)方法
formatHMS() 是long类型时间(秒级)转 天/小时/分钟/秒 大家可以添加下方法
/**
* String类型日期(2021-12-07 10:00:00)转时间戳(秒级)
*
* @param time
* @return
*/
public static long timeToStampSecond(String time) {
Date d = new Date();
long timeStamp = 0;
try {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
d = sf.parse(time);// 日期转换为时间戳
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
timeStamp = d.getTime() / 1000;
return timeStamp;
}
/**
* 秒转天/小时/分钟/秒
*
* @param value
* @return
*/
public static String formatHMS(long value) {
String str = "";
long i = value / (3600 * 24);
if (i > 0) {
str = i + "天";
}
value %= 3600 * 24;
i = value / 3600;
if (i > 0) {
str += i + "时";
}
value %= 3600;
i = value / 60;
if (i > 0) {
str += i + "分";
}
value %= 60;
str += value + "秒";
return str;
}
WRONG_TIMES和WRONG_DURATION是我添加的枚举类字段 方便之后更改限制次数和时间
//密码错误次数
public static int WRONG_TIMES = 3;
//密码解锁时长(分钟)
public static int WRONG_DURATION = 5;
现在接口都已写好 就看在哪里使用了
打开SysLoginService.java
/**
*登录验证
*/
public String login(String username, String password, String code, String uuid) {
.......
.......
Authentication authentication = null;
try {
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername/decryptByPrivateKey()私钥解密
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());
}
}
//登陆成功后记得清除错误次数
userService.cleanErrorNums(username);
......
......
}
验证错误次数的方法 要放在catch中 因为只是在密码误时引用 如果放在try中 会影响UserDetailsServiceImpl中的账号判断
登陆成功后记得清除错误次数 方法要放在try catch后面
最后大家需要跟具自己代码做调整 有什么问题欢迎留言讨论😋