java实现用户登录异常统计、锁定及解锁功能
写在前面
现在很多互联网项目、app等都会有登录异常提醒、登录异常次数限制,基本都是5次异常后就会锁定一定时间的账户,让该账户无法进行登录操作。需要注册用户使用安全验证手段(如动态验证码等),解除锁定后才能进行新一轮登录操作。这么做无非就是两个目的:
1. 为了注册用户的账户安全;
2. 更多的是防止黑客攻击,维护网站等的安全。
项目需求
项目开发结束,进入测试阶段,很多项目或者开发就进入到了边测试边修改bug的阶段了。不过,我们公司的项目还需要进行安全测试,对不符合要求的项目需要进行安全隐患的整改。这不,我就“被迫”的研究起了如何避免被无差别攻击,防止通过无限攻击次数、穷举等破解密码的手段了。
整体思路
1. 验证及提示
获取并验证账号登录异常信息,提示登录异常。代码片段如下
// 验证账户是否封锁String usercode = userLoginDto.getUsercode();ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();// 如果这个账号登录异常,则在登录页面提醒。String shiroLoginCount = opsForValue.get(SHIRO_LOGIN_COUNT + usercode);if (!StringUtils.isEmpty(shiroLoginCount) && Integer.parseInt(shiroLoginCount) >= 10) {if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK + usercode))) {// 计数大于10次,设置用户被锁定5分钟String msg = "由于输入错误次数大于10次,帐号5分钟内已经禁止登录!";log.info(msg);return PlatformResult.failure(msg);}}
2. 计数
用户的每次异常登录(其实就是密码错误)时,将账户及错误次数记录到redis中。代码片段如下
// 登录失败计数opsForValue.increment(SHIRO_LOGIN_COUNT + usercode, 1); // 每次增加1log.info(usercode + ":账号登录异常次数:" + opsForValue.get(SHIRO_LOGIN_COUNT + usercode));
3. 检查及限制
检查累积阈值错误次数(本系统设置为10次)时,锁定该用户,并限制一定时间内(本系统设置为5分钟)无法进行登录操作。代码片段如下
// 登录异常次数超限实现锁定if (Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT + usercode)) >= 10) {opsForValue.set(SHIRO_IS_LOCK + usercode, "LOCK"); // 锁住这个账号,值是LOCK。stringRedisTemplate.expire(SHIRO_IS_LOCK + usercode, 5, TimeUnit.MINUTES); // expire 变量存活期限}
4. 解锁及清空
在错误10次(可根据业务需要进行调整)前登录成功,则解锁该用户异常状态,清空登录错误次数和解锁账户。代码片段如下
/*** 清空登录计数* * @author: caip* @date: 2021-04-07 15:45:57* @param userName*/private void clearLoginCount(String userName) {log.info("UserLoginServiceImpl clearLoginCount start");ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();// 清空登录计数opsForValue.set(SHIRO_LOGIN_COUNT + userName, "0");// 清空锁opsForValue.set(SHIRO_IS_LOCK + userName, "");log.info("UserLoginServiceImpl clearLoginCount end");}
整体代码
1. 参数对象
用于前后交互传参
package cn.xxx.rdc.fi.dto;import lombok.Getter;
import lombok.Setter;/*** @author xxx* @date: 2021-02-25 10:54:02* @Copyright: Copyright (c) 2006 - 2021* @Company: 公司* @Version: V1.0*/
@Getter
@Setter
public class UserLoginDto {/** 账号. */private String usercode;/** 密码. */private String password;/** 记住密码. */private String remarkid;/** 用户类型. */private Integer userType;/** 微信userid. */private String wxuserid;/** 是否加密:1.是;0.否. */private String isEncryption;
}
2. 接口
定义交互标准
@Autowiredprivate IUserLoginService userLoginService;@ApiOperation(value = "用户登录", notes = "用户登录")@PostMapping("/userLogin")public PlatformResult<JSONObject> userLogin(@RequestBody UserLoginDto userLoginDto, ServletResponse response) {return userLoginService.userLogin(userLoginDto, response);}
3. 服务定义
定义服务标准
/*** 用户登录* * @author: xxx* @date: 2021-02-24 17:17:23* @param userLoginDto 用户登录对象* @return*/PlatformResult<JSONObject> userLogin(UserLoginDto userLoginDto, ServletResponse response);
4. 服务实现
定义服务具体实现
package cn.xxx.rdc.knowledge.service.impl;import java.util.concurrent.TimeUnit;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSONObject;import cn.xxx.BootComm.Contants;
import cn.xxx.BootComm.utils.PlatformResult;
import cn.xxx.rdc.knowledge.dto.UserLoginDto;
import cn.xxx.rdc.knowledge.service.IUserService;
import cn.xxx.rdc.knowledge.utils.HttpClientUtil;
import cn.xxx.rdc.knowledge.utils.HttpRequestUtil;
import cn.xxx.rdc.knowledge.utils.RSAUtil;
import lombok.extern.slf4j.Slf4j;/*** 用户登录服务类* * @author xxx* @date: 2021-02-24 17:17:53* @Copyright: Copyright (c) 2006 - 2021* @Company: xxx* @Version: V1.0*/
@Slf4j
@Service
public class UserServiceImpl implements IUserService {@Value("${user.login.url}")private String userLoginUrl;@Value("${sso.verifyUrl}")private String ssoVerifyUrl;@Value("${sso.defaultPrikey}")private String ssoDefaultPrikey;@Value("${user.logout.url}")private String userLogoutUrl;@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 用户登录次数计数 redisKey 前缀private final String SHIRO_LOGIN_COUNT = "kb_shiro_login_count_";// 用户登录是否被锁定 redisKey 前缀private final String SHIRO_IS_LOCK = "kb_shiro_is_lock_";@Overridepublic PlatformResult<JSONObject> userLogin(UserLoginDto userLoginDto, ServletResponse response) {log.info("UserLoginServiceImpl userLogin start");// 获取HttpServletRequest、HttpServletResponseHttpServletResponse res = (HttpServletResponse)response;// 验证账户是否封锁String usercode = userLoginDto.getUsercode();ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();// 如果这个账号登录异常,则在登录页面提醒。String shiroLoginCount = opsForValue.get(SHIRO_LOGIN_COUNT + usercode);if (!StringUtils.isEmpty(shiroLoginCount) && Integer.parseInt(shiroLoginCount) >= 10) {if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK + usercode))) {// 计数大于10次,设置用户被锁定5分钟String msg = "由于输入错误次数大于10次,帐号5分钟内已经禁止登录!";log.info(msg);return PlatformResult.failure(msg);}}// 未封锁,则进行登录请求userLoginDto.setIsEncryption("1");String params = JSONObject.toJSONString(userLoginDto);log.info("userLogin urlStr=[" + userLoginUrl + "]");log.info("userLogin params=[" + params.toString() + "]");// 调用登录http请求String result = HttpRequestUtil.httpCrossDomain(userLoginUrl, params);// 获取登录返回参数JSONObject resultJson = JSONObject.parseObject(result);int statusCode = resultJson.getIntValue("statusCode");boolean success = resultJson.getBooleanValue("success");// 判断是否登录成功String loginMsg = resultJson.getString("message");if (statusCode != 200 || !success) {// 登录失败计数opsForValue.increment(SHIRO_LOGIN_COUNT + usercode, 1); // 每次增加1log.info(usercode + ":账号登录异常次数:" + opsForValue.get(SHIRO_LOGIN_COUNT + usercode));// 登录异常次数超限实现锁定if (Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT + usercode)) >= 10) {opsForValue.set(SHIRO_IS_LOCK + usercode, "LOCK"); // 锁住这个账号,值是LOCK。stringRedisTemplate.expire(SHIRO_IS_LOCK + usercode, 5, TimeUnit.MINUTES); // expire 变量存活期限}return PlatformResult.failure(loginMsg);}// 登录成功解锁this.clearLoginCount(usercode);// 查询本系统用户信息JSONObject userObject = resultJson.getJSONObject("object");// 判断是否免密登录String tokenEncryption = userObject.getString("token");log.info("tokenEncryption=" + tokenEncryption);// 判断是否调用免密登录if (!StringUtils.isEmpty(tokenEncryption) && 36 < tokenEncryption.length()) {// 通过加密token,解密后调用sso接口获取用户信息String token = RSAUtil.decrypt(ssoDefaultPrikey, tokenEncryption);log.info("token=" + token);// 调用登录http请求String userResult = HttpClientUtil.getInstance().sendHttpGet(ssoVerifyUrl + "?token=" + token);log.info("userResult=" + userResult);// 获取登录返回参数resultJson = JSONObject.parseObject(userResult);String code = resultJson.getString("code");// 判断是否登录成功loginMsg = resultJson.getString("msg");if (!"0".equals(code)) {return PlatformResult.failure(loginMsg);}userObject = resultJson.getJSONObject("uid");}// 设置cookie中的THPMSCookie为登录用户的tokenres.addCookie(new Cookie(Contants.COOKIE_NAME, userObject.getString("token")));log.info("UserLoginServiceImpl userLogin result=[" + userObject.toString() + "]");log.info("UserLoginServiceImpl userLogin end");return PlatformResult.success(userObject);}/*** 清空登录计数* * @author: caip* @date: 2021-04-07 15:45:57* @param userName*/private void clearLoginCount(String userName) {log.info("UserLoginServiceImpl clearLoginCount start");ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();// 清空登录计数opsForValue.set(SHIRO_LOGIN_COUNT + userName, "0");// 清空锁opsForValue.set(SHIRO_IS_LOCK + userName, "");log.info("UserLoginServiceImpl clearLoginCount end");}}
补充说明
以上代码有部分业务代码,整合了自用的SSO(单点登录系统)用于验证账户密码正确性,使用时只需要关心锁定和解锁功能,其他代码直接删除或替换为业务代码即可。
java实现用户登录异常统计、锁定及解锁功能相关推荐
- java ee用户登录_EE Servlet 3:使用会话和过滤器开发用户登录
java ee用户登录 我在上一篇文章中介绍了Application类,您可以在其中设置后端服务. 我添加的一个示例服务是UserService . 该服务将加载包含用户名和密码集的Java用户属性文 ...
- Java 实现用户登录项目
Java 实现用户登录项目 需求: 在页面中要求输入用户名和密码,并显示验证码.在三项都通过验证后显示登录成功否则登录失败 分析; 在验证用户名密码之前应该先判断验证码是否通过验证,防止多次连接数据库 ...
- Liunx创建新用户登录异常:/usr/bin/xauth: error/timeout in locking authority file /home/liuqidong/.Xauthority
Liunx创建新用户登录异常:/usr/bin/xauth: error/timeout in locking authority file /home/liuqidong/.Xauthority 问 ...
- 国产化DM达梦数据库 - 用户状态查询、锁定与解锁,“登录失败次数超过限制”问题解决
达梦数据库密码输入错误达到限制后会被锁定一段时间. An error occurred while establishing the connection:Long Message: 登录失败次数超过 ...
- java实现用户登录注册功能(用集合框架来实现)
需求:实现用户登录注册功能(用集合框架来实现) 分析: A:需求的类和接口 1.用户类 UserBean 2.用户操作方法接口和实现类 UserDao UserDaoImpl 3.测试类 UserTe ...
- qq浏览器网页版_QQ邮箱回应部分用户登录异常:系后台服务波动,问题已解决...
5月6日消息,针对用户反映QQ邮箱登录异常情况,腾讯QQ邮箱官方回应称,因后台服务波动,部分用户出现登录异常情况,目前问题已解决. 5月6日上午,有网友反映QQ邮箱崩溃,换浏览器依然无法登录,PC端和 ...
- PostgreSQL用户登录失败自动锁定的解决办法
墨墨导读:PostgreSQL使用session_exec插件实现用户密码验证失败几次后自动锁定,本文介绍一种处理方案. 一.插件session_exec安装配置篇 下载插件并编译安装. https: ...
- Java游戏用户登录注册_Java实现多用户注册登录的幸运抽奖
本文实例为大家分享了Java实现简单幸运抽奖的具体代码,供大家参考,具体内容如下 代码模块: User类: package test1; public class User { private Str ...
- linux用户登录失败,锁定用户
2019独角兽企业重金招聘Python工程师标准>>> #vim /etc/pam.d/login auth required pam_tally2.so deny=3 unlock ...
最新文章
- 菜鸟脱壳之脱壳的基础知识(六)——手动查找IAT和修复Dump的程序
- matlab模拟gpd,如何用ARMA模型预测中国GDP
- 一文带你看懂分布式软总线在家庭场景的应用
- 第三次学JAVA再学不好就吃翔(part6)--基础语法之char数据类型
- 基于套接字SOCKET的及时聊天
- 回文三位数(信息学奥赛一本通-T1155)
- Linux之python3编译安装
- Delphi 程序开发范例宝典(第2版)高清PDF下载 附光盘
- 长沙android工程师,长沙安卓工程师辅导
- 商务口语:议价时可能用到的句子
- IntelliJ IDEA使用技巧(三)——Debug 篇
- Javascript的一种代码结构方式——插件式
- 咪咕:笔试题(20190916)
- 如何测算信息化项目软件运维费?
- linux找回cp之前的文件,Linux中找回误删除的文件
- 普渡大学计算机图形,美国:普渡大学(UX方向)
- inputBox 与 Application.inputBox 的用法与区别。
- Anaconda入门:安装及包与环境的管理(conda命令)
- 重视六大职场面试礼仪
- fatal error: zlib.h: No such file or directory