系统登录失败次数超过限定次数,则根据IP或用户名锁定,需要过了锁定时间才可以继续登录

这篇具有很好参考价值的文章主要介绍了系统登录失败次数超过限定次数,则根据IP或用户名锁定,需要过了锁定时间才可以继续登录。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

之前做的项目都有用户名锁定机制,即:用户名失败次数超过多少次,就锁定这个用户不可以再登录,需要等过了锁定时间才可以继续登录。

然后最近的一个项目中,有个漏洞整改措施中,提到了这个锁定机制不能只根据用户名锁定,还要根据IP锁定。

两种锁定机制

1、根据用户名锁定

根据用户名锁定的前提是这个用户名要存在,在数据库中查出了这个用户名,我们才能记录错误次数,从而判断是否应该锁定。那假如这个用户名不存在,那就不用记录错误次数,那是不是可以一直调用登录接口?想象一下,某个恶意IP一直无限次调用登录接口,每次调用都会查询一次数据库,这。。。。。。


2、根据IP锁定

如果只根据IP锁定的话,就可能会存在某些恶意用户通过不停切换IP的方式,对某个用户名的密码进行猜测的情况。因为IP锁定只能对IP进行锁定,而无法对具体的用户名进行锁定。


综合以上两种锁定机制的缺点,所以我们不能只采用其中一种,而是需要结合两种锁定方式来进行校验。

实现流程

既然知道了两种机制单独使用的缺点,那我们的实现流程也好梳理了。

1、不管用户名是否存在,先校验IP是否已经锁定,锁定了不执行后面的流程,直接提示错误信息。


2、 如果IP没有被锁定,则先查询用户名,用户名不存在,则记录IP错误次数+1,并判断错误次数是否达到限定次数,达到则锁定,并提示错误信息;该IP下一次登录时,则会执行第一步。


3、 如果用户名存在,则校验用户名是否已经锁定,锁定了不执行后面的流程,直接提示错误信息。


4、如果用户名没有被锁定,则校验密码是否正确,密码错误,则同时记录IP错误次数+1、用户名错误次数+1,并判断错误次数是否达到限定次数,达到则锁定,并提示错误信息;该IP或用户名下一次登录时,则会执行第一步或第三步。


5、如果密码正确,则清除当前用户名和IP的错误次数记录。

以上五点中,其中第四点关于判断错误次数是否达到限定次数,我是同时要获取IP错误次数和用户名错误次数,取这两个次数中较大的那一个为准,如果相等就随便取一个。

假如IP错误次数较大,则IP锁定了,那不管你用哪个用户名只要是这个IP,就都不允许登录;如果是用户名错误次数较大,则用户名锁定了,那不管你怎么改变IP,只要是这个用户名,就不允许登录。

代码

梳理好了流程,那我们就可以开始写代码了

登录的controller

/** 密码最大错误次数 */
private int ERROR_COUNT = 3;
/** 锁定时长 */
private String LOCK_DURATION = "15";

@PostMapping("/login")
public ResultUtil login(String userName, String password,HttpServletRequest request){
    String ip = IPUtil.getIpAddress(request);
    long currentTime = System.currentTimeMillis();
    lockedUser(currentTime, ip,"IP"); //判断ip是否锁定
    //保存登录日志
    SysLog sysLog = new SysLog(ip,"用户登录","login");
    sysLog.setId(IdUtil.getSnowflakeNextIdStr());
    sysLog.setState("登录成功");
    try {
        //私钥解密
        userName = RSAUtil.decrypt(userName);
        password = RSAUtil.decrypt(password);

        sysLog.setCreatorId(userName);
        SysSafe safe = sysSafeService.list().get(0);
        SysUser user = passwordErrorNum(ip,userName, password,safe);// 先查询用户名是否存在,不存在则校验IP,存在则校验用户名和密码
        int i = safe.getIdleTimeSetting(); //如果系统闲置时间为0,设置token和session永不过期
        String token = "";
        if (i==0){
            token = LoginUtil.login(user,null,2592000);// 最长保持登录为30天
        }else {
            token = LoginUtil.login(user);
        }
        sysLog.setInfo(userName+"登录成功");
        sysLogService.save(sysLog);
        return ResultUtil.success(token);
    } catch (ExceptionVo e) {
        sysLog.setInfo(e.getMessage());
        sysLog.setState("登录失败");
        sysLogService.save(sysLog);
        return ResultUtil.error(e.getCode(),e.getMessage());
    }catch (Exception e) {
        sysLog.setInfo(BaseConstant.UNKNOWN_EXCEPTION);
        sysLog.setState("登录失败");
        sysLogService.save(sysLog);
        e.printStackTrace();
        return ResultUtil.error(BaseConstant.UNKNOWN_EXCEPTION);
    }
}

// ......省略其他接口
// 注意,如果有获取验证码或获取公钥的接口(这两个接口都是在登录页面加载时、调用登录接口之前调用的),也需要先校验IP是否锁定,锁定了不给返回新数据。如下:
/*String ip = IPUtil.getIpAddress(request);
long currentTime = System.currentTimeMillis();
lockedUser(currentTime, ip,"IP"); //判断ip是否锁定*/

/**
 * 判断账号或IP是否锁定
 */
private boolean lockedUser(long currentTime,String userName,String msg){
    boolean flag = false;
    if (RedisUtil.hasKey(BaseConstant.ERROR_COUNT+userName)){
        long loginTime = Long.parseLong(RedisUtil.hget(BaseConstant.ERROR_COUNT+userName, "loginTime").toString());
        String isLocaked = RedisUtil.hget(BaseConstant.ERROR_COUNT+userName,"isLocaked").toString();
        if ("true".equals(isLocaked) && currentTime < loginTime){
            Duration between = LocalDateTimeUtil.between(LocalDateTimeUtil.of(currentTime), LocalDateTimeUtil.of(loginTime));
            throw new ExceptionVo(1004,msg+"锁定中,还没到允许登录的时间,请"+between.toMinutes()+"分钟后再尝试");
        }else{
            flag = true;
            RedisUtil.hset(BaseConstant.ERROR_COUNT+userName,"isLocaked","false");//重置为false
        }
    }
    return flag;
}

/**
 * 账号和密码错误次数验证
 */
private SysUser passwordErrorNum(String ip,String userName, String password,SysSafe sysSafe) throws InvalidKeySpecException, NoSuchAlgorithmException {
    //查询用户
    SysUser user = sysUserService.getUser(null,userName);
    if (null == user){ // 根据用户名查询用户,如果没有查到,则根据ip校验
        checkIPLocked(sysSafe,ip);
    }
    long currentTime = System.currentTimeMillis();
    boolean flag = lockedUser(currentTime, userName,"账号");//判断账号是否锁定
	//根据前端输入的密码(明文),和加密的密码、盐值进行比较,判断输入的密码是否正确
    boolean authenticate = EncryptionUtil.authenticate(password, user.getPassword(), user.getSalt());
    if (authenticate) {
        //密码正确错误次数和IP错误次数清零
        RedisUtil.del(BaseConstant.ERROR_COUNT+userName);
        RedisUtil.del(BaseConstant.ERROR_COUNT+ip);
    } else {
        checkNameLocked(sysSafe,userName,ip,flag);
    }
    return user;
}

/**
 * 校验IP锁定
 */
public boolean checkIPLocked(SysSafe sysSafe,String ip){
    long currentTime = System.currentTimeMillis();
    boolean flag = lockedUser(currentTime, ip,"IP");//判断IP是否锁定
    //错误3次,锁定15分钟后才可登陆 允许时间加上定义的登陆时间(毫秒)
    long timeStamp = System.currentTimeMillis()+900000;
    //密码登录限制(0:连续错3次,锁定账号15分钟。1:连续错5次,锁定账号30分钟)
    if (sysSafe.getPwdLoginLimit()==1){
        ERROR_COUNT = 5;
        LOCK_DURATION = "30";
        //错误5次,锁定30分钟后才可登陆 允许时间加上定义的登陆时间(毫秒)
        timeStamp = System.currentTimeMillis()+1800000;
    }
    if (RedisUtil.hasKey(BaseConstant.ERROR_COUNT+ip)){
        int i = Integer.parseInt(RedisUtil.hget(BaseConstant.ERROR_COUNT+ip,"errorNum").toString());
        if (flag && i==ERROR_COUNT){ // 当错误次数达到限定次数时,走到这一步说明已经过了锁定时间再次登录,这时重新将错误次数设置为1
            RedisUtil.hset(BaseConstant.ERROR_COUNT+ip,"errorNum",1);
        }else {
            RedisUtil.hincr(BaseConstant.ERROR_COUNT+ip,"errorNum",1);// 错误次数加一
        }
        RedisUtil.hset(BaseConstant.ERROR_COUNT+ip,"loginTime",timeStamp);
    }else {
        Map<String,Object> map = new HashMap<>();
        map.put("errorNum",1);
        map.put("loginTime",timeStamp);
        map.put("isLocaked","false");// 是否锁定,默认为false
        RedisUtil.hmset(BaseConstant.ERROR_COUNT+ip, map, -1);
    }
    int i = Integer.parseInt(RedisUtil.hget(BaseConstant.ERROR_COUNT+ip,"errorNum").toString());
    if (i==ERROR_COUNT){
    	// 将锁定状态改为true表示已锁定
       	RedisUtil.hset(BaseConstant.ERROR_COUNT+ip,"isLocaked","true");
        throw new ExceptionVo(1004,"用户名或密码错误"+ERROR_COUNT+"次,现已被锁定,请"+LOCK_DURATION+"分钟后再尝试");
    }
    throw new ExceptionVo(1000,"用户名或密码错误,总登录次数"+ERROR_COUNT+"次,剩余次数: " + (ERROR_COUNT-i));
}

/**
 * 校验用户名锁定
 */
public boolean checkNameLocked(SysSafe sysSafe,String userName,String ip,boolean flag){
    //错误3次,锁定15分钟后才可登陆 允许时间加上定义的登陆时间(毫秒)
    long timeStamp = System.currentTimeMillis()+900000;
    //密码登录限制(0:连续错3次,锁定账号15分钟。1:连续错5次,锁定账号30分钟)
    if (sysSafe.getPwdLoginLimit()==1){
        ERROR_COUNT = 5;
        LOCK_DURATION = "30";
        //错误5次,锁定30分钟后才可登陆 允许时间加上定义的登陆时间(毫秒)
        timeStamp = System.currentTimeMillis()+1800000;
    }
    if (RedisUtil.hasKey(BaseConstant.ERROR_COUNT+userName)){
        int i1=0,i2=0;
        if (RedisUtil.hasKey(BaseConstant.ERROR_COUNT+userName))
            i1 = Integer.parseInt(RedisUtil.hget(BaseConstant.ERROR_COUNT+userName,"errorNum").toString());
        if (RedisUtil.hasKey(BaseConstant.ERROR_COUNT+ip))
            i2 = Integer.parseInt(RedisUtil.hget(BaseConstant.ERROR_COUNT+ip,"errorNum").toString());
        // 每一次错误,同时记录当前IP和用户名的错误次数
        if (flag && (i1==ERROR_COUNT || i2==ERROR_COUNT)){ // 走到这一步说明已经过了锁定时间再次登录,这时重新将错误次数设置为1
            if (i1>i2){ // i1 > i2 是用户名错误次数到达限定次数,将用户名的错误次数重置为1
                RedisUtil.hset(BaseConstant.ERROR_COUNT+userName,"errorNum",1);
            }else if (i2>i1){ // i2 > i1 是IP错误次数到达限定次数,将IP的错误次数重置为1
                RedisUtil.hset(BaseConstant.ERROR_COUNT+ip,"errorNum",1);
            }else { // 否则就是用户名和IP错误次数相等,将两个的错误次数同时重置为1
                RedisUtil.hset(BaseConstant.ERROR_COUNT+userName,"errorNum",1);
                RedisUtil.hset(BaseConstant.ERROR_COUNT+ip,"errorNum",1);
            }
        }else {
            RedisUtil.hincr(BaseConstant.ERROR_COUNT+userName,"errorNum",1);
            RedisUtil.hincr(BaseConstant.ERROR_COUNT+ip,"errorNum",1);
        }
        RedisUtil.hset(BaseConstant.ERROR_COUNT+userName,"loginTime",timeStamp);
        RedisUtil.hset(BaseConstant.ERROR_COUNT+ip,"loginTime",timeStamp);
    }else {
        Map<String,Object> map = new HashMap<>();
        map.put("errorNum",1);
        map.put("loginTime",timeStamp);
        map.put("isLocaked","false");
        RedisUtil.hmset(BaseConstant.ERROR_COUNT+userName, map, -1);
        if (!RedisUtil.hasKey(BaseConstant.ERROR_COUNT+ip)){
            RedisUtil.hmset(BaseConstant.ERROR_COUNT+ip, map, -1);
        }
    }
    int i1 = Integer.parseInt(RedisUtil.hget(BaseConstant.ERROR_COUNT+userName,"errorNum").toString());
    int i2 = Integer.parseInt(RedisUtil.hget(BaseConstant.ERROR_COUNT+ip,"errorNum").toString());
    int i = i1 >= i2 ? i1 : i2;// 取错误次数大的那个值进行判断
    if (i1==ERROR_COUNT || i2==ERROR_COUNT){ // 任意一个满足,将值大的那个设置为锁定
        if (i1>i2){ // i1 > i2 是用户名错误次数到达限定次数,将用户名的锁定状态设置为锁定
            RedisUtil.hset(BaseConstant.ERROR_COUNT+userName,"isLocaked","true");
        }else if (i2>i1){ // i2 > i1 是IP错误次数到达限定次数,将IP的锁定状态设置为锁定
            RedisUtil.hset(BaseConstant.ERROR_COUNT+ip,"isLocaked","true");
        }else { // 否则就是用户名和IP错误次数相等,将两个的锁定状态同时设置为锁定
            RedisUtil.hset(BaseConstant.ERROR_COUNT+userName,"isLocaked","true");
            RedisUtil.hset(BaseConstant.ERROR_COUNT+ip,"isLocaked","true");
        }
        throw new ExceptionVo(1004,"用户名或密码错误"+ERROR_COUNT+"次,现已被锁定,请"+LOCK_DURATION+"分钟后再尝试");
    }
    throw new ExceptionVo(1000,"用户名或密码错误,总登录次数"+ERROR_COUNT+"次,剩余次数: " + (ERROR_COUNT-i));
}

以上我们就实现了用户名和IP一起校验的锁定机制了。这两种方式结合校验的机制应该是挺完善的,按照上面的代码,我自己测试也是没啥问题的,当然可能我代码也会有遗漏的,欢迎大家评论补充。

最后,如果这篇文章你觉得写得还行或者对你有点帮助的话,欢迎给点个大拇指~文章来源地址https://www.toymoban.com/news/detail-663378.html

到了这里,关于系统登录失败次数超过限定次数,则根据IP或用户名锁定,需要过了锁定时间才可以继续登录的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • linux设置登录失败处理功能(密码错误次数限制、pam_tally2.so模块)和操作超时退出功能(/etc/profile)

    1、登录失败处理功能策略(服务器终端) (1)编辑系统/etc/pam.d/system-auth 文件,在 auth 字段所在的那一部分添加如下pam_tally2.so模块的策略参数: 或者 (2)pam_tally2.so模块参数解读: onerr=fail         #表示连续错误 deny=n            #表示连续登录失败次数超过n次后拒绝访

    2023年04月13日
    浏览(48)
  • 数组中出现次数超过一半的数字

    数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 假设数组非空,并且一定存在满足条件的数字。 思考题 : 假设要求只能使用 O(n) 的时间和额外 O(1) 的空间,该怎么做呢? 数据范围 数组长度 [1,1000] 。 样例  代码:

    2024年02月13日
    浏览(40)
  • C++信息学奥赛1186:出现次数超过一半的数

    这段代码的作用是判断给定的整数数组中是否存在出现次数超过一半的元素。首先,通过循环输入整数数组的元素。然后,通过两层循环遍历数组,外层循环逐个元素进行统计,内层循环计算当前元素在数组中出现的次数。在内部循环中,如果发现有元素出现次数超过了数组

    2024年02月10日
    浏览(38)
  • 剑指 Offer 39.数组中出现次数超过一半的数字

    剑指 Offer 39.数组中出现次数超过一半的数字 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入:nums = [3,2,3] 输出:3 示例 2: 输入:n

    2024年01月21日
    浏览(38)
  • 剑指offer39.数组中出现次数超过一半的数字

    这个题非常简单,解法有很多种,我用的是HashMap记录每个元素出现的次数,只要次数大于数组长度的一半就返回。下面是我的代码: 题解还有一种更牛逼的解法,把数组排序,然后返回数组中间的那个数就行,因为如果这个数出现的次数大于数组长度的一半的话,排完序后

    2024年02月13日
    浏览(36)
  • 剑指 Offer 39. 数组中出现次数超过一半的数字

    🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。 🚁 个人主页:不 良 🔥 系列专栏:🛸剑指 Offer  🛹Linux 📕 学习格言:博观而约取,厚积而薄发 🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸

    2024年02月07日
    浏览(41)
  • 每日一题——LeetCode1287.有序数组中出现次数超过25%的元素

    方法一 一次循环统计 题目给出的数据相同的元素都是相邻的,那么直接从头开始遍历,统计每种元素出现次数,当有元素次数超过arr.length/4即为要求的元素   消耗时间和内存情况: 方法二 方法一简化版 设步长step=arr.length/4,如果某个元素arr[i],跨越一个步长后arr[i+step],即

    2024年01月22日
    浏览(50)
  • 修改电脑用户名(使用注册表),解决电脑无法正常登录账号,电脑桌面文件消失,修改注册表恢复文件后正常,但电脑使用后续出现很多问题,重装系统

    有时候会因为电脑用户名是中文,程序编译异常,就想到去修改电脑用户名,百度出来,可以去注册表里修改 win+r打开输入regedit,找到路径计算机HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionProfileListS-1-5-21-2491506228-1445222362-3903811352-1001 修改中文路径文件  修改后,左边这

    2024年02月04日
    浏览(137)
  • ESXI 系统密码登录失败登录不上

    自从esxi版本升级到6.5+ 以后,经常遇到\\\"用户名或密码不正确,无法完成登录的报错\\\"。 这个问题曾经长时间困扰着it同事们,曾经有人发过帖子esxi6.5存在bug,升级到U3 或更高版本就可以了。而出现这个问题有时候缺非常诡异: web界面无法登录,但console控制台接显示器可以正常登录

    2024年02月06日
    浏览(68)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包