1. 程式人生 > >關於限制同一個IP訪問頻率和限制使用者登入時候輸錯密碼次數限制(超過即限制)

關於限制同一個IP訪問頻率和限制使用者登入時候輸錯密碼次數限制(超過即限制)

一:關於限制同一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的基本操作。