今天给两个项目加上了登录时在一定时间内输入密码多次失败就锁定账号的功能,锁定之后需要管理员进行解锁才能够继续登陆;两个项目一个是用shiro,一个没用shiro;不过都差不多,下面只讲用shiro的,具体业务可以根据自己需求去修改(比如:登录失败多次不锁定账号,只是以相对时间内的次数去判断,超过该段时间就可以继续登陆;时间自己设定)
数据库:建立一个表存放用户登录失败的记录(密码最好加密后再存进数据库)
/*
* 登录信息和用户验证信息验证(non-Javadoc)
* @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
HttpServletRequest request=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
List<String> principals = new ArrayList<>();
int retValue = checkUserPwd(request,username, password, principals);
switch (retValue) {
case 0: return new SimpleAuthenticationInfo(principals, password, getName());
case 1: throw new UnknownAccountException("用户名不存在");
case 2: throw new IncorrectCredentialsException("密码不正确");
case 3: throw new AuthenticationException("初始化用户信息失败");
case 4: throw new DisabledAccountException("该用户已被禁用");
default: throw new AuthenticationException("校验用户名密码异常");
}
}
private int checkUserPwd(HttpServletRequest request,String username, String password, List<String> principals) {
String ip = RequestUtil.getIP(request);
try {
ChaUser chaUser = chaUserService.getByUsername(username);
if(chaUser == null) {
logger.info(String.format("用户名[%s]不存在", username));
return 1;
}
String dbPassword = chaUser.getPassword();
if(chaUser == null) {
logger.info(String.format("账号[%s]不存在", username));
return 1;
}
if (chaUser.getStatus() == Status.DISABLED.getId()) {
logger.info(String.format("账号[%s]已被禁用", username));
return 4;
}
dbPassword = chaUser.getPassword();
if (!password.equalsIgnoreCase(dbPassword)) {
if(userLoginFailRecordService.checkFailCount(username, password, ip)) {
chaUserService.updateStatus(chaUserService.getByUsername(username).getUserId(), AccountStatus.LOCKED.getId());
}
logger.info(String.format("用户[%s]的密码[%s]不正确", username, password));
return 2;
}
principals.add(chaUser.getUserId().toString());
principals.add(Const.SHIRO_PREFIX_UN + chaUser.getUsername());
return 0;
} catch (Exception ex) {
logger.error(username + " checkUserPwd exception", ex);
return 99;
}
}
public int add(String account, String password, String ip) throws Exception {
UserLoginFailRecord userLoginFailRecord = new UserLoginFailRecord();
userLoginFailRecord.setAccount(account);
userLoginFailRecord.setPassword(password);
userLoginFailRecord.setIp(ip);
userLoginFailRecord.setCreateTime(new Date());
return (int) dao.findForObject("UserLoginFailRecordMapper.add", userLoginFailRecord);
}
public boolean checkFailCount(String account, String password, String ip) throws Exception {
UserLoginFailRecord userLoginFailRecord = new UserLoginFailRecord();
userLoginFailRecord.setAccount(account);
userLoginFailRecord.setPassword(password);
userLoginFailRecord.setIp(ip);
userLoginFailRecord.setCreateTime(new Date());
dao.findForObject("UserLoginFailRecordMapper.add", userLoginFailRecord);
Date startTime = new Date(new Date().getTime() - 24 * 60 * 60 * 1000); // 24小时
PageData pd = new PageData();
pd.put("account", account);
pd.put("startTime", startTime);
long count = (long) dao.findForObject("UserLoginFailRecordMapper.count", pd);
return count >= 5;
}
mapper.xml文件简单就不贴了。一些枚举等根据自己具体业务。
总体思路就是:每登录失败一次就在登录失败表中记录一次记录(存储ip是为了分析原因能够用到);当在规定时间内登录失败一定次数再次登录失败则触发账号锁定(一般需要后台有账号解锁功能;若要写成登录失败一段时间冷却之后能继续试的话,可以根据登录失败表的次数跟时间判断,规定时间内没达到次数就可以继续尝试)