java实现登陆失败n次锁定账户,y分钟后自动解锁,两次失败间隔时间过大则不计次数(含redis处理)

1. 功能说明

功能使用springboot框架完成,主要就是作用就是当用户登陆错误次数过多(本文登陆3次失败,锁定账户30分钟),将锁定账户,在30分钟后自动解除,并且如果两次错误登陆时间大于指定的时间差(本文为了演示,使用1分钟来代替),就只更新错误登陆时间,不更新错误登陆次数。
登陆次数,锁定时间以及登陆错误时间间隔应该放在配置文件中,本文为了演示,暂时写在代码中,后续上线部署就需要放在配置文件中来获取

2.代码层面

(1)实体类(SysUserDO)

	//多余的实体属性可能不需要,已隐藏
    @ApiModelProperty("id")
    private Long id;

    @Null(message = "用户登陆名称不能为空")
    @ApiModelProperty("用户登陆名称")
    private String userName;

    @Null(message = "用户名称不能为空")
    @ApiModelProperty("用户名称")
    private String name;

    @Null(message = "用户密码不能为空")
    @ApiModelProperty("用户密码")
    private String password;

    @ApiModelProperty("用户部门ID")
    private Long deptId;

    @ApiModelProperty("用户状态(1:正常状态,2:用户被锁定)")
    private String status;

    @ApiModelProperty("是否删除  1:删除,0:未删除")
    private String del;

    @ApiModelProperty("登陆锁定状态  1:正常,2:锁定")
    private String loginStatus;

    @ApiModelProperty("最后登陆错误时间")
    private Date lastLoginErrorTime;

    @ApiModelProperty("登陆错误次数")
    private Integer loginErrorCount;

(2)ServiceImpl实现

public class LoginServiceImpl implements LoginService {

    private static final Logger logger = LoggerFactory.getLogger(LoginServiceImpl.class);


    @Autowired
    private LoginLogMapper loginLogMapper;

    @Autowired
    private SysUserDao sysUserDao;

    @Autowired
    private SysMenuDao sysMenuDao;


    @Override
    public LoginResponse login(String userName,String password) {
        if (StringUtils.isBlank(userName) || StringUtils.isBlank(password)) {
            recordLogininfor(userName, LoginLog.FAIL, "用户/密码为空");
            throw new BizException(BizExceptionEnum.USER_NAME_PWD_NULL);
        }
        SysUserDO user = sysUserDao.getByUserName(userName);

        if (Objects.isNull(user)) {
            recordLogininfor(userName, LoginLog.FAIL, "用户不存在");
            throw new BizException(BizExceptionEnum.USER_NOT_FOUND);
        }
        if (user.getDel().equals(Constant.YES)) {
            recordLogininfor(userName, LoginLog.FAIL, "用户被删除");
            throw new BizException(BizExceptionEnum.AUTH_USER_DEL_OR_LOCKED);
        }
        if (user.getStatus().equals(UserStatus.DISABLE.getCode())) {
            recordLogininfor(userName, LoginLog.FAIL, "用户被锁定");
            throw new BizException(BizExceptionEnum.USER_LOCKED);
        }

        //用户锁定后,时间到了解锁,时间未到抛出异常信息
        if (user.getLoginStatus().equals("2")) {
            long errorTime = user.getLastLoginErrorTime().getTime();
            long nowTime = System.currentTimeMillis();
            long time = (nowTime - errorTime) / (1000 * 60);
            if (30 - time <= 0) {
                user.setLastLoginErrorTime(null);
                user.setLoginStatus("1");
                user.setLoginErrorCount(0);
                sysUserDao.update(user);
            } else {
                recordLogininfor(userName, LoginLog.FAIL, "用户登陆被锁定中");
                throw new BizException("当前用户登陆已锁定,请" + (30 - time) + "分钟后重试");
            }
        }
      
        //密码错误开始
        if (!user.getPassword().equalsIgnoreCase(password)) {
            SysUserDO userErr = sysUserDao.getByUserName(userName);
            //获取错误登陆
            Integer loginErrorCount = userErr.getLoginErrorCount();
            //获取上次登陆错误时间
            Date lastLoginErrorTime = userErr.getLastLoginErrorTime();
            //获取用户是否登陆锁定   锁定  1:正常,2:锁定
            String loginStatus = userErr.getLoginStatus();
            if (loginErrorCount == 3) {
                userErr.setLoginStatus("2");
                sysUserDao.update(userErr);
                recordLogininfor(userName, LoginLog.FAIL, "用户密码错误次数达到上限,已锁定");
                throw new BizException("当前用户登陆已锁定,请30分钟后重试");
            } else {

                //如果登陆次数为0,代表还没有登陆失败记录
                if (loginErrorCount == 0) {
                    userErr.setLoginErrorCount(loginErrorCount + 1);
                    userErr.setLastLoginErrorTime(new Date());
                    sysUserDao.update(userErr);
                    recordLogininfor(userName, LoginLog.FAIL, "用户密码错误");
                    throw new BizException("登陆失败,可尝试登陆次数为:" + (3 - (loginErrorCount))); //
                } else {
                    long timeError = userErr.getLastLoginErrorTime().getTime();
                    long timeNow = System.currentTimeMillis();
                    long time = (timeNow - timeError) / (1000 * 60);
                    //如果两次的登陆失败的时间差大于n分钟,则不计错误次数,此处用1分钟代替
                    if (time >= 1) {
                        if (loginErrorCount == 1) {
                            userErr.setLastLoginErrorTime(new Date());
                            sysUserDao.update(userErr);
                            recordLogininfor(userName, LoginLog.FAIL, "用户密码错误");
                            throw new BizException("登陆失败,可尝试登陆次数为:" + 3);
                        } else {
                            userErr.setLoginErrorCount(loginErrorCount);
                            userErr.setLastLoginErrorTime(new Date());
                            sysUserDao.update(userErr);
                            recordLogininfor(userName, LoginLog.FAIL, "用户密码错误");
                            throw new BizException("登陆失败,可尝试登陆次数为:" + (3 - (loginErrorCount)));
                        }
                    } else {
                        userErr.setLoginErrorCount(loginErrorCount + 1);
                        userErr.setLastLoginErrorTime(new Date());
                        sysUserDao.update(userErr);
                        recordLogininfor(userName, LoginLog.FAIL, "用户密码错误");
                        throw new BizException("登陆失败,可尝试登陆次数为:" + (3 - (loginErrorCount)));
                    }
                }
            }


        }
        // 获取用户角色和菜单权限
        List<SysMenuVO> sysMenuList = sysMenuDao.listVisibleByUserId(user.getId());

        // 登陆
        StpUtil.login(user.getId());
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        recordLogininfor(userName, LoginLog.SUCCESS, "登陆成功");
        //一旦有登陆成功的,将错误次数置空
        user.setLastLoginErrorTime(null);
        user.setLoginStatus("1");
        user.setLoginErrorCount(0);
        sysUserDao.updateLogin(user);
        return new LoginResponse(tokenInfo.getTokenName(), tokenInfo.getTokenValue(), user.getUserName(),
                list2tree(sysMenuList), user.getId());
    }
	//层级关系转化
    public List<SysMenuVO> list2tree(List<SysMenuVO> list) {
        List<SysMenuVO> tree = new ArrayList<SysMenuVO>();

        for (SysMenuVO permission : list) {

            if (0 == permission.getPid().longValue()) {
                tree.add(permission);
            }

            for (SysMenuVO child : list) {
                if (permission.getId().longValue() == child.getPid().longValue()) {
                    child.setpName(permission.getName());
                    if (null == permission.getChild()) {
                        permission.setChild(new ArrayList<SysMenuVO>());
                    }
                    permission.getChild().add(child);
                }
            }
        }
        return tree;
    }


    /**
     * 记录登录信息
     */
    public void recordLogininfor(String username, String status, String message) {
        LoginLog loginLog = new LoginLog();
        loginLog.setLoginTime(new Date());
        loginLog.setUserName(username);
        loginLog.setAddress(IpUtils.getIpAddr(ServletUtils.getRequest()));
        loginLog.setComment(message);
        loginLog.setStatus(status);
        loginLogMapper.insert(loginLog);
    }

当然这块也可以使用redis来做存储可以减少数据库压力
主要代码如下:

if (!user.getPassword().equalsIgnoreCase(password)) {
                //判断redis是否存在key,因为我们这块是userName具有唯一性,故可以作为key
                Boolean aBoolean = redisTemplate.hasKey("LoginName:" + userName);
                if(aBoolean){
                    String errorLoginCount = redisTemplate.opsForValue().get("LoginName:" + userName).toString();
                    Integer newerrorLoginCount = Integer.parseInt(errorLoginCount);
                    //key为 LoginName: userName  value是对应的错误登陆次数
                    if(newerrorLoginCount.intValue() == 3){
                        Long expire = redisTemplate.opsForValue().getOperations().getExpire("LoginName:" + userName);
                        if(expire.intValue()>0){
                            recordLogininfor(userName, LoginLogDO.FAIL, "用户登陆被锁定中");
                            throw new BizException("当前用户锁定中,请" + expire / 60 + "分钟后重试");
                        }
                    }else {
                        redisTemplate.opsForValue().set("LoginName:" + userName, newerrorLoginCount+1);
                        if(3 - newerrorLoginCount - 1==0){
                            recordLogininfor(userName, LoginLogDO.FAIL, "用户登陆被锁定");
                            redisTemplate.opsForValue().getOperations().expire("LoginName:" + userName, 1800, TimeUnit.SECONDS);
                            throw new BizException("当前用户已锁定,请30分钟后重试");
                        }
                        recordLogininfor(userName, LoginLogDO.FAIL, "密码错误");
                        throw new BizException("密码错误,还有" + (3 - newerrorLoginCount-1) + "次机会");
                    }
                }else {
                    recordLogininfor(userName, LoginLogDO.FAIL, "密码错误");
                    redisTemplate.opsForValue().set("LoginName:" + userName, "1");
                    throw new BizException("密码错误,还有" + 2 + "次机会");
                }
            }else {
                //密码正确,判断redis里面是否有值,有值就代表锁定中
                Boolean aBoolean = redisTemplate.hasKey("LoginName:" + userName);
                if(aBoolean){
                    String errorLoginCount = redisTemplate.opsForValue().get("LoginName:" + userName).toString();
                    Integer newerrorLoginCount = Integer.parseInt(errorLoginCount);
                    if(newerrorLoginCount.intValue() ==3){
                        Long expire = redisTemplate.opsForValue().getOperations().getExpire("LoginName:" + userName);
                        if(expire.intValue()>0){
                            recordLogininfor(userName, LoginLogDO.FAIL, "用户登陆被锁定中");
                            throw new BizException("当前用户锁定中,请" + expire / 60 + "分钟后重试");
                        }
                    }else {
                        //直接删除key
                        redisTemplate.delete("LoginName:" + userName);
                    }
                }
            }
  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值