前言
针对用户登录未作限制,批量爆破成功密码登陆系统的漏洞问题。
将以下个方法再嵌入你登录方法里面的合适位置。
下面是我嵌入位置参考:
@RequestMapping(value = "/login", method = RequestMethod.POST)
public @ResponseBody
String loginPost(HttpServletRequest request, HttpServletResponse response,
HttpSession session) {
Integer socketProt = properties.getObmSocketPort();口
String socketIP = properties.getObmServiceIp();
String username = request.getParameter("username");
String password = request.getParameter("password");
ObmUser ou = null;
try {
//获取当前登录用户信息
ou = monitor.selectObmUserByUsername(CryptJsUtil.decrypt(username, KEY, IV));
} catch (Exception e) {
e.printStackTrace();
}
if (ou != null) {
String userN = ou.getUsername();
String result = null;
try {
//第一个嵌入位置:效验账号是否锁定
if(!checkLock(session, userN)) {
return "该账号已被锁定5分钟!"
}
result = userService.login(CryptJsUtil.decrypt(username, KEY, IV), CryptJsUtil.decrypt(password, KEY, IV));
} catch (Exception e) {
e.printStackTrace();
}
if ("登录失败!".equals(result)) {
//第二个嵌入位置:新增用户登录失败次数
addFailNum(session, userN);
return "登录失败!";
} else {
try {
logservice.save(new Log(ou.getId(), ou.getName() + "登录系统", Utils.parseDate()));
} catch (ManagerException e1) {
e1.printStackTrace();
}
session.setAttribute("user", ou);
session.setAttribute("socketIP", socketIP);
session.setAttribute("socketProt", socketProt);
//第三个嵌入位置:清理用户登录失败的记录
cleanFailNum(session, userN);
return "true";
}
}
return "当前用户不存在!";
}
代码实现
1.校验用户登录失败次数
num为登录次数
timeDifference 为锁定时间(min)
两个变量可以动态配置
/**
* 校验用户登录失败次数
* @param session
* @param user
* @return
*/
private boolean checkLock(HttpSession session, String user) {
Object obj = session.getServletContext().getAttribute(user);
if (obj == null) {
return true;
}
JSONObject json = JSON.parseObject(JSON.toJSONString(obj));
Integer num = json.getInteger("num");
Date date = json.getDate("lastDate");
long timeDifference = ((new Date().getTime() - date.getTime()) / 60 / 1000);
return num < 5 || timeDifference >= 2;
}
2.新增用户登录失败次数
/**
* 新增用户登录失败次数
* @param session
* @param user
*/
private void addFailNum(HttpSession session, String user) {
Object obj = session.getServletContext().getAttribute(user);
JSONObject json;
int num = 0;
if (obj == null) {
json = new JSONObject();
} else {
json = JSON.parseObject(JSON.toJSONString(obj));
num = json.getInteger("num");
Date date = json.getDate("lastDate");
long timeDifference = ((new Date().getTime() - date.getTime()) / 60 / 1000);
if (timeDifference >= 5) {
num = 0;
}
}
json.put("num", num + 1);
json.put("lastDate", new Date());
session.getServletContext().setAttribute(user, json);
}
3.新增用户登录失败次数
/**
* 新增用户登录失败次数
* @param session
* @param user
*/
private void addFailNum(HttpSession session, String user) {
Object obj = session.getServletContext().getAttribute(user);
JSONObject json;
int num = 0;
if (obj == null) {
json = new JSONObject();
} else {
json = JSON.parseObject(JSON.toJSONString(obj));
num = json.getInteger("num");
Date date = json.getDate("lastDate");
long timeDifference = ((new Date().getTime() - date.getTime()) / 60 / 1000);
if (timeDifference >= 5) {
num = 0;
}
}
json.put("num", num + 1);
json.put("lastDate", new Date());
session.getServletContext().setAttribute(user, json);
}
总结
防止爆破登录的解决方案之一,优点在于不用操作数据库,对项目爆破登录漏洞进行解决,通过HttpSession会话作用域进行用户以及登录次数进行存储记录(登录次数以及账户锁定时间可以动态配置)。其缺点是更换浏览器登录,登陆限制次数将重新记录。
这种实现方式比较简单,适合维护现场要求不高的项目。爆破登录也可以通过采用验证码机制,在设置验证码的时候一定和用户名密码一块在服务器端做校验,同时要保证验证码的时效性,一个验证码只能使用一次,使用完之后,立马销毁。