关于限制同一个IP访问频率和限制用户登录时候输错密码次数限制(超过即限制)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/PSY_God/article/details/80714849

一:关于限制同一IP的基本的思路

spring action请求频率限制(不能限制静态资源的请求)

 限制同一ip在一定时间内, 对server请求的次数.
 由ip第一次请求来做为时间点, 将时间,请求次数缓存到redis.
1. 第一次请求(redis中无缓存记录), 初始化缓存(时间=当前, 次数=1) .
 2. 非第一次请求, 从redis中取出缓存与当前时间相比.
      2.1: 缓存时间过期(小于当前-intervalInMS), 同 1.处理

      2.2: 缓存时间有效, 缓存请求次数+1, 如果请求次数>=requestMaxCount, 请求过于频繁, 不交于action处理.

二:示例代码,采用的是SpringMVC的过滤器

public class FrequencyInterceptor extends HandlerInterceptorAdapter {

    private long intervalInMS = 10000L;//请求时间段, 以ms为单位
    private int requestMaxCount = 30;//时间段内, 请求次数上限, 其余将不会处理
    private Logger logger = LoggerFactory.getLogger(FrequencyInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //请求频率限制
        String ip = this.getIP(request);
        if(StringUtils.isEmpty(ip)){
            logger.error("未获取到目标ip");
            return true;
        }

        //数据缓存至redis中
        long currentTimeInMS = Calendar.getInstance().getTimeInMillis();
        String frequencyDetail = RedisPoolsUtil.get(redisKey(ip));
        if(StringUtils.isEmpty(frequencyDetail)){ //redis中未查询到记录
            logger.info("ip: {}, 第一次请求", ip);
            //初始化信息到redis
            resetRedisCache(ip, currentTimeInMS, 1);
            return true;
        }


        //不是第一次请求, 先比较时间
        JSONObject obj = JSONObject.parseObject(frequencyDetail);
        long requestTime = obj.getLong("requestTime");
        if(currentTimeInMS-requestTime >= this.intervalInMS){ //时间间隔较长, 重新记数
            logger.info("ip: {}, 间隔过长, 重新计数", ip);
            resetRedisCache(ip, currentTimeInMS, 1);
            return true;
        }

        //有效计时时间内
        int requestCount = obj.getIntValue("requestCount");
        if(requestCount > this.requestMaxCount){ //请求过于频繁
            logger.info("{} 请求过于频繁", ip);
            return false;
        } else{
            //有效计数时间内, 计数+1
            logger.debug("ip: {}, 请求计数: {}", ip, requestCount);
            resetRedisCache(ip, requestTime, requestCount + 1);
        }
        return true;
    }

    /*
     * @description: 获取请求ip
     * @date: 2018/6/12
     * @param:
     * @return:
     */
    private String getIP(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
            int index = ip.indexOf(",");
            if(index != -1){
                return ip.substring(0,index);
            }else{
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
            return ip;
        }
        return request.getRemoteAddr();
    }

    /*
     * @description: 缓存到redis中的key
     * @date: 2018/6/12
     * @param:
     * @return:
     */
    private String redisKey(String ip){
        return "interceptor.frequency." + ip;
    }

    /*
     * @description: 重设redis缓存
     * @date: 2018/6/12
     * @param:
     * @return:
     */
    private void resetRedisCache(String ip, long currentTimeInMS, int count){
        //初始化信息到redis
        JSONObject obj = new JSONObject();
        obj.put("requestTime", currentTimeInMS);
        obj.put("requestCount", count);
        RedisPoolsUtil.set(redisKey(ip), obj.toJSONString());
    }
}

用户密码在五分钟内输错超过四次即锁定用户(不采用数据库进行持久化计数)。

一:基本的思路是

1:同样使用redis作为缓存

2:redis中保存时间戳数据和计数数据

3:同样的对比时间戳的大小。

二:代码如下图(该代码为用户输入的密码与数据库的密码不同,即输错了密码且在五分钟内输错了四次,即锁定用户!!)

if (!encryptedPassword.equals(user.getPassword())) {
    // 用户校验不通过
    int limitNumber = 1;
    //第一次输错密码的记录时间保存在redis中
    Long loginTime = Calendar.getInstance().getTimeInMillis();
    log.info("用户登录,帐号={},认证不通过", account);
    if (StringUtil.isEmpty(limitIs) || "null".equals(limitIs)) {
        jedis.set(account + "limit", limitNumber + "#" + loginTime);
    } else {
        //记录密码错误的次数
        final long fiveMillis = 300000L;
        limitNumber = Integer.valueOf(limitIs.split("#")[0]);
        //第一次输错密码的时间戳
        Long loginTimes = Long.valueOf(limitIs.split("#")[1]);
        //当前输入密码的时间戳
        Long nowTime = Calendar.getInstance().getTimeInMillis();
        //五分钟以内的时间输错的密码计数器+1,更新redis的值
        if (limitNumber < 4 && (nowTime - loginTimes) < fiveMillis) {
            limitNumber += 1;
            jedis.set(account + "limit", limitNumber + "#" + loginTimes);
        }
        //五分钟以内输错四次锁定用户
        if (limitNumber >= 4 && (nowTime - loginTimes) < fiveMillis) {
            //数据库中锁定用户
            int a = userService.lockUsers(account);
            if (a == 1) {
                log.info("用户因5分钟以内输错密码次数超过四次被锁定:{}", account);
            }

        }
        //超过五分钟的时候,再次输入密码的时候将redis的值重置为1
        if ((nowTime - loginTimes) > fiveMillis) {
            jedis.set(account + "limit", 1 + "#" + nowTime);
            limitNumber = 1;
        }
    }
    if (!StringUtil.isEmpty(limitIs) && !"null".equals(limitIs) && limitNumber >= 4) {
        rst.setResultCode(ReqResult.resultCode_login_error);
        rst.setReturnObject("因多次输入密码错误被锁定,请联系管理员解锁");
        return ReqResultUtil.genResultResponse(rst);
    } else {
        rst.setResultCode(ReqResult.resultCode_login_error);
        rst.setReturnObject("密码错误,您已输错" + limitNumber + "次,还有" + (4 - limitNumber) + "次机会");
        return ReqResultUtil.genResultResponse(rst);
    }
}

三:当用户继续登录且连续输错四次密码被锁定的时候(提示操作)

if (user.getStatus() == 0 && !StringUtil.isEmpty(limitIs) && !"null".equals(limitIs) && Integer.valueOf(limitIs.split("#")[0]) >= 4) {
    log.info("用户登录,帐号={},用户因多次输入密码已经禁用", account);
    // 帐号多次输入错误密码被禁用
    rst.setResultCode(ReqResult.resultCode_user_forbid);
    rst.setReturnObject("用户因多次输入密码错误已经禁用,请联系管理人员");
    return ReqResultUtil.genResultResponse(rst);
}

四:当用户在输错密码未超过四次的时候,如最后一次机会输入的密码是对的。此刻将redis的数据重置。(采用的String 类型的“null”)

rst.setResultCode(ReqResult.RESULT_CODE_SUCCESS);
rst.setReturnObject("用户验证通过");
if (!StringUtil.isEmpty(limitIs) && !"null".equals(limitIs)) {
    jedis.set(account + "limit", "null");
}

以上便是两种限制的基本思路,和实际项目的代码。涉及到Springmvc的过滤器的使用。redis的基本操作。


猜你喜欢

转载自blog.csdn.net/PSY_God/article/details/80714849