摘要:主要通过sql(oracle)实现连续X次输错密码后,禁止登录。Y小时或隔天后可以登录。
在javaweb项目的登录模块中经常会有连续X次输错密码后禁止登录的需求。这个功能可以通过多种方法来实现。本文只介绍以sql为主的方法,以供参考。
这是从实际项目中扒出来的代码,对一些变量名进行了处理,但是文中将包含全部核心代码。使用框架为struts2,ibatis。
需求:连续输入错误密码5次后,账号进入锁定状态,不可登录。锁定状态将于1小时后,或者下一个自然日(0点)解除。
具体方法描述。
1.建一张表来记录用户id,错误登录次数,上次登录时间。(也可以在user表上添加这三个字段,这样会省去一个初始化的麻烦)
2.登录失败后,进行校验:若已经错误登录5次,返回特定编码以告知前台显示错误信息。若次数不足5次,错误登录次数+1,并返回正常错误信息。若距离上一次错误登录时间已经过了1小时或者到了第二天,错误次数置为1。
3.若密码正确,并可以正常登录,将错误次数置为0。若处于锁定中,返回指定错误信息。
代码
建表语句
create table log_pwd_err(login_id number primary key, err_num number default 0, last_date date default sysdate);
java代码
//参数的设置
private static int maxTime = 5; //连续5次后
private static double hour = 1; //一小时内不可登录。
/**
* 登录错误5次,一小时后不可登录
* @param loginId 登录id
* @param password 密码
* @param isJiaMi 对密码进行加密处理
* @return
* @throws DataAccessException
*/
public Map validateByLoginIdLoginErr(String loginId, String password,
boolean isJiaMi) throws DataAccessException {
Map resultMap = new HashMap(2);
User user = getByLoginId(loginId); //一个通过登录id获取用户实体类的方法,文中无源码
String encodePass = "";
if (isJiaMi && null != isJiaMi) {
encodePass = encrypt.encode(password); //对密码进行加密处理,文中无源码
}
if(null != user){ //如果登录id确实存在
if(user.getPassword().equals(encodePass)){ //并且密码输入正确
if(this.checkWhenPwdOk(user)){ //校验该用户是否可以登录
this.setErrZero(user); //如果可以确实的登录,则对表中错误登录次数置零
resultMap.put("user", user);
return resultMap;
}else {
resultMap.put("extMsg", "errMax"); //前台错误信息展现由上层代码实现。
return resultMap;
}
}else {
if(this.checkErrNum(user)){ //密码错误时的一系列操作
//返回true时,错误次数超过了5次,需要返回特定的错误信息
resultMap.put("extMsg", "errMax");
return resultMap;
}else {
//错误次数没有超过5次,返回一个空map,交由上层处理
return resultMap;
}
}
}
return resultMap;
}
private void setErrZero(User user){
//将指定id的错误次数置零
//client是操作数据库的方法,可以认为是SqlMapClientBuilder.buildSqlMapClient(Resources.getResourceAsReader("config/SqlMap.xml")); 这种东西。
this.client.update("login.err.updateForOk", user);
}
/**
* 即使密码正确,也不能登录
* @param user
* @return false 处于锁定中,无法登陆 true 可以正常登录
*/
private boolean checkWhenPwdOk(User user){
Map daoMap = new HashMap();
daoMap.put("loginId", user.getLoginId() + "");
daoMap.put("maxTime", maxTime + "");
daoMap.put("hour", hour + "");
String currentNumStr = (String) this.client.queryForObject("login.err.checkWhenPwdOk", daoMap);
if(currentNumStr == null || currentNumStr.length() == 0){
return true; //err表中无数据,可以登录成功
}
if(Integer.valueOf(currentNumStr) >= maxTime){ //超过5次
return false;
}else {
return true;
}
}
//密码错误时的方法
private boolean checkErrNum(User user){
Map daoMap = new HashMap();
daoMap.put("loginId", user.getLoginId() + "");
daoMap.put("maxTime", maxTime + "");
daoMap.put("hour", hour + "");
String currentNumStr = (String) this.client.queryForObject("login.err.getCurrentNum", daoMap);
if(currentNumStr == null || currentNumStr.length() == 0){
//错误信息表与用户表并不是同步的,如果是新建的用户,err表中将没有对应数据,需要插入一条新数据
this.client.insert("login.err.insertUser", daoMap);
currentNumStr = "0";
}
int currentNum = Integer.valueOf(currentNumStr);
if(currentNum >= maxTime){
//若次数超过了,将不会修改登陆时间。那样做会导致一小时的校验错误
return true;
}
//执行+1或=1的操作
this.client.update("login.err.updateForErr", daoMap);
return false;
}
sql文件
select decode(trunc(t1.last_date), trunc(sysdate),
case when (sysdate - t1.last_date) * 24 > to_number(#hour#) and t1.err_num >= to_number(#maxTime#)
then 1
else t1.err_num end,
1) "flag"
from log_pwd_err t1
where t1.login_id = to_number(#loginId#)
insert into log_pwd_err(login_id, err_num, last_date) values (#userId#, 1, sysdate)
update log_pwd_err t1
set t1.err_num = 0,
t1.last_date = sysdate
where t1.login_id = to_number(#loginId#)
and t1.err_num != 0
update log_pwd_err t1
set t1.err_num = decode(trunc(t1.last_date), trunc(sysdate),
case when (sysdate - t1.last_date) * 24 > to_number(#hour#)
and t1.err_num >= to_number(#maxTime#)
then 1
else t1.err_num + 1 end,
1),
t1.last_date = sysdate
where t1.login_id = to_number(#loginId#)
select case
when trunc(t1.last_date) != trunc(sysdate) then
0
when to_number(#hour#) / 24 > sysdate - t1.last_date and t1.err_num >= to_number(#maxTime#) then
t1.err_num
else
0
end "flag"
from log_pwd_err t1
where t1.login_id = to_number(#loginId#)
总结:使用sql去完成这种需求最大的好处是逻辑清晰,集中,安全,并且不会出现bug。整个功能连写带测试加部署只花了2小时,比写这篇文章都短……缺点就是会对数据库造成一点点额外的压力。