2018年9月13日 星期四

AP防禦實作

1.Filter
@Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
   throws IOException, ServletException {
  // TODO Auto-generated method stub
  if (loginStatusCache.getDefendStatus().equals("Y")) {
   // loginStatusCache = LoginStatusCache.getInstance(limitVisitTime,
   // measurementTimeInSeconds, blockDurationTimeInSeconds);
   String customerIP = HostAddressUtils.getRealIPAddresses((HttpServletRequest) request);
   Map<String, String> newUserInfo = null;
   Map<String, String> userInfo = null;
   if (loginStatusCache.get(customerIP) == null) {
    try {
     // lock.writeLock().lock();
     lock.lock();
     // 建立新使用者info.
     newUserInfo = createNewUserInfo(customerIP);
     userInfo = (Map<String, String>) loginStatusCache.putIfAbsent(customerIP, newUserInfo);

    } finally {
     lock.unlock();
    }
    // 此行不需加入鎖定
    checkIfBlock(userInfo == null ? newUserInfo.get("blockStatus") : userInfo.get("blockStatus"), request,
      response, chain, customerIP);
   } else {
    String blockStatus = null;
    try {
     lock.lock();
     blockStatus = loginStatusCache.addVisitTimes(customerIP);
    } finally {
     lock.unlock();
    }
    checkIfBlock(blockStatus, request, response, chain, customerIP);
   }
  } else {
   chain.doFilter(request, response);
  }

 }

 /***
  * 建立連入使用者資訊,包括登入時間、瀏覽次數、是否被鎖定等資訊
  * 
  * @return Map<String, String> userInfo
  */
 private Map<String, String> createNewUserInfo(String ip) {
  // 塞入userInfo的資訊(loginTime, visitTimes, blockStatus)
  Map<String, String> userInfo = new HashMap<String, String>();
  userInfo.put("ip", ip);
  userInfo.put("loginTime", String.valueOf(System.currentTimeMillis()));
  userInfo.put("visitTimes", "1");
  userInfo.put("blockStatus", "false");
  return userInfo;

 }

 /***
  * 依據blockStatus決定是否回應正常的頁面 "false":正常流程; "true":回應limit字串
  * 
  * @param blockStatus
  * @param request
  * @param response
  * @param chain
  * @throws IOException
  * @throws ServletException
  */
 private void checkIfBlock(String blockStatus, ServletRequest request, ServletResponse response, FilterChain chain, String ip)
   throws IOException, ServletException {
  response.setContentType("application/json;charset=utf-8");
  if (blockStatus.equalsIgnoreCase("true")) {
   PrintWriter pw = response.getWriter();
   //pw.println(String.format(new String("此IP[%s]已被锁定".getBytes("UTF-8"),"UTF-8") , ip));
   pw.println(String.format("此IP[%s]已被锁定" , ip));
   pw.flush();
   pw.close();
  } else
   chain.doFilter(request, response);

 }
2.LoginStatus
public class LoginStatusCache {
 private static Logger log = LogUtils.pay;
 private ReadWriteLock lock = new ReentrantReadWriteLock();
 private static volatile LoginStatusCache instance = null;
 private volatile Map<String, Map<String, String>> cacheMap = null; // key:登入的IP,
                  // value:
                  // Map:一分鐘內瀏覽次數,是否block狀態,第一次進入頁面時間
 private String defendStatus = "N";
 private int limitVisitTime = 0;
 private int measurementTimeInSeconds = 0; 
 private int blockDurationTimeInSeconds = 0; 
 private int reloadDefendParamsTimeInSeconds = 0; 
 private String nextReloadTime = null;

 private LoginStatusCache(int limitVisitTime, int measurementTimeInSeconds, int blockDurationTimeInSeconds,
   String defendStatus, int reloadDefendParamsTimeInSeconds) {
  this.defendStatus = defendStatus;
  this.limitVisitTime = limitVisitTime;
  this.measurementTimeInSeconds = measurementTimeInSeconds;
  this.blockDurationTimeInSeconds = blockDurationTimeInSeconds;
  this.reloadDefendParamsTimeInSeconds = reloadDefendParamsTimeInSeconds;
  

  log.debug("[LOG]defendStatus:" + defendStatus);
  log.debug("[LOG]limitVisitTime:" + limitVisitTime + " 次");
  log.debug("[LOG]measurementTimeInSeconds:" + measurementTimeInSeconds + " 秒");
  log.debug("[LOG]blockDurationTimeInSeconds:" + blockDurationTimeInSeconds + " 秒");
  log.debug("[LOG]reloadDefendParamsTimeInSeconds:" + reloadDefendParamsTimeInSeconds + " 秒");

  cacheMap = new ConcurrentHashMap<String, Map<String, String>>();
  Thread cleanerJob = new Thread(new UserInfoMapCleaner());
  cleanerJob.setDaemon(true);
  cleanerJob.start();
 }

 public static LoginStatusCache getInstance(int limitVisitTime, int measurementTimeInSeconds,
   int blockDurationTimeInSeconds, String defendStatus, int reloadDefendParamsTimeInSeconds) {
  if (instance == null) {
   synchronized (LoginStatusCache.class) {
    if (instance == null) {
     instance = new LoginStatusCache(limitVisitTime, measurementTimeInSeconds,
       blockDurationTimeInSeconds, defendStatus, reloadDefendParamsTimeInSeconds);
    }
   }
  }
  return instance;
 }

 
 public static LoginStatusCache getInstance() {
  return instance;
 }

 /***
  * 若指定的key沒有指定的value(userInfo Map),就將userInfo存入cacheMap中(ip當成key)
  * 並且回傳null,若存在則取出指定key對應的userInfo並將visitTimes累加1
  */
 public Object putIfAbsent(String ip, Map<String, String> newUserInfo) {
  @SuppressWarnings("unchecked")
  Map<String, String> userInfo = (Map<String, String>) cacheMap.putIfAbsent(ip, newUserInfo);

  if (userInfo != null) {
   this.addVisitTimes(ip);
  }
  return userInfo;
 }

 /***
  * 對傳入的ip將其一分鐘內的瀏覽次數(visitTimes)+1,若超過一分鐘內的瀏覽限制次數, 則將
  * blockStatus設成"true",並且填入開始block的時間(startBlockTime)
 
  */
 public String addVisitTimes(String ip) {
  Map<String, String> userInfo = (Map<String, String>) cacheMap.get(ip);

  if (userInfo != null) {
   if (userInfo.get("blockStatus").equalsIgnoreCase("true"))
    return userInfo.get("blockStatus");

   int visitTimes = Integer.parseInt(userInfo.get("visitTimes")) + 1;
   userInfo.put("visitTimes", String.valueOf(visitTimes));
   if (visitTimes > this.limitVisitTime) {
    userInfo.put("blockStatus", "true");
    userInfo.put("startBlockTime", String.valueOf(System.currentTimeMillis()));
   }
  }
  return userInfo.get("blockStatus");
 }

 public Map<String, String> get(String key) {
  return cacheMap.get(key);
 }

 public void put(String key, Map<String, String> value) {
  cacheMap.put(key, value);
 }

 public Map<String, String> getAndRemove(String key) {
  return cacheMap.remove(key);
 }

 public Map<String, Map<String, String>> getUserLoginCacheMap() {
  return cacheMap;
 }

 public String getDefendStatus() {
  try{
   lock.readLock().lock();
   return defendStatus;
  }finally{
   lock.readLock().unlock();
  }
  
 }
 
 public int getLimitVisitTime() {
  return limitVisitTime;
 }
 
 public int getMeasurementTimeInSeconds() {
  return measurementTimeInSeconds;
 }

 public int getBlockDurationTimeInSeconds() {
  return blockDurationTimeInSeconds;
 }

 public String getNextReloadTime() {
  try{
   lock.readLock().lock();
   return nextReloadTime;
  }finally{
   lock.readLock().unlock();
  }
  
 }

 public int getReloadDefendParamsTimeInSeconds() {
  return reloadDefendParamsTimeInSeconds;
 }

 public void setReloadDefendParamsTimeInSeconds(int reloadDefendParamsTimeInSeconds) {
  this.reloadDefendParamsTimeInSeconds = reloadDefendParamsTimeInSeconds;
 }






 class UserInfoMapCleaner implements Runnable {
  // private boolean isCheck = true;

  long checkDurationInMillis = (measurementTimeInSeconds) * 1000; // 設定job多久去檢查一次cacheMap(設定60秒,基本上至少要大於等於measurementTimeInSeconds)
  long lastLoadingParamsTime = System.currentTimeMillis(); // 進來前剛載入過參數,所以假設最近一次重載時間是現在
  long nextLoadingParamsTime = lastLoadingParamsTime + reloadDefendParamsTimeInSeconds * 1000;

  private final String DEFENSE_ENABLED = "N";
  private final int DEFAULT_DEFENSE_MAX_ACCESS_TIMES = 60; // 
  private final int DEFAULT_MEASUREMENT_TIME_IN_SECONDS = 60; // 
  private final int DEFAULT_BLOCK_DURATION_TIME_IN_SECONDS = 1 * 60; // 

  @Override
  public void run() {
   // TODO Auto-generated method stub
   FunctionExecuteTimer timer = new FunctionExecuteTimer();
   
   while (true) {
    //檢查是否該重新載入防禦參數(每10分鐘重載一次)
    checkIfReloadDefendParams();
    try {
     Thread.sleep(checkDurationInMillis);
    } catch (InterruptedException e1) {
     // TODO Auto-generated catch block
     e1.printStackTrace();
    }
    
    if (defendStatus.equals("Y")){
     FunctionExecuteTimer.setLogMessage("清除cacheMap花費時間:");
     log.debug("==============開始清除防禦快取 cacheMap==============");
     timer.start();
     Collection<Map<String, String>> tempUsers = cacheMap.values();
     log.debug("開始清除前,cacheMap Size:" + cacheMap.size());
     long now = System.currentTimeMillis();
     tempUsers.forEach(obj -> {
      Map<String, String> userInfo = (Map<String, String>) obj;
      String ip = userInfo.get("ip");
      int visitTimes = Integer.parseInt(userInfo.get("visitTimes"));
      long loginTime = Long.parseLong(userInfo.get("loginTime"));
      long duration;
      if (userInfo.get("blockStatus").equalsIgnoreCase("false")) {
       // duration:從使用者第一次登入後至目前為止,瀏覽次數不能超過limitVisitTimeInOneMinute,duration即為登入期間
       // 檢查是否有超過量測時間,visitTimes還沒超過限制次數的,將其從cacheMap移除
       if ((duration = DateUtil.secondsBetween(loginTime, now)) > measurementTimeInSeconds) {
        if (visitTimes <= limitVisitTime) {
         log.debug(String.format("%s 已經登入%s次(%s秒)還未超過瀏覽次數限制[%s]", ip, visitTimes, duration,
           limitVisitTime));
         tempUsers.remove(obj);
         log.debug(String.format("從cacheMap移除IP:%s的UserInfo,cacheMap Size:%s", ip,
           cacheMap.size()));
        } else {
         // 登入時間超過時間限制,且瀏覽次數也超過限制
         // 在addVisitTimes裡面就會判斷是否超過limitVisitTimeInOneMinute,超過就將blockStatus改成true
        }
       } else { // do nothing.
        // 在addVisitTimes裡面就會判斷是否超過limitVisitTimeInOneMinute,超過就將blockStatus改成true
       }
      } else {
       // 檢查block時間到的使用者,將其從cacheMap中移除
       long startBlockTime = Long.parseLong(userInfo.get("startBlockTime"));
       long actualBlockTime = DateUtil.secondsBetween(startBlockTime, now);
       if (actualBlockTime >= blockDurationTimeInSeconds) {
        tempUsers.remove(obj);
        log.debug(String.format("%s已經被block %s分鐘,從cacheMap移除IP:%s的UserInfo,cacheMap Size:%s",
          ip, actualBlockTime / 60, ip, cacheMap.size()));
       }
      }
     });
     log.debug(String.format("==============結束清除防禦快取 cacheMap,cacheMap Size:%s ==============", cacheMap.size()));
     timer.stop();
    }else{
     cacheMap.clear();
     log.info(String.format("防禦關閉,清除所有用戶連線記錄,cacheMap Size:%s", cacheMap.size()));
    }
   }

  }

  /***
   * 檢查是否該重新載入防禦參數(每隔10分鐘重新載入一次)
   */
  private void checkIfReloadDefendParams() {
   Date now = new Date();
   String nowTime = DateUtil.formatDate(now, "yyyy-MM-dd HH:mm:ss");
   
   if (nextReloadTime == null){
    try{
     lock.writeLock().lock();
     nextReloadTime = DateUtil.formatDate(new Date(nextLoadingParamsTime), "yyyy-MM-dd HH:mm:ss");
    }finally{
     lock.writeLock().unlock();
    }
   }
   
   log.info(String.format("目前檢查時間:%s, 預估於%s 重新載入防禦參數", nowTime, nextReloadTime));
   if (now.getTime() >= nextLoadingParamsTime) {
    try{
     lock.writeLock().lock();
     String thisReloadTime = DateUtil.formatDate(new Date(nextLoadingParamsTime), "yyyy-MM-dd HH:mm:ss");
     lastLoadingParamsTime = nextLoadingParamsTime;
     nextLoadingParamsTime = lastLoadingParamsTime + reloadDefendParamsTimeInSeconds*1000;
     nextReloadTime = DateUtil.formatDate(new Date(nextLoadingParamsTime), "yyyy-MM-dd HH:mm:ss");
     //log.info("目前時間:"+ nowTime);
     log.info(String.format("開始重新載入防禦參數, 下一次重新載入時間%s", thisReloadTime, nextReloadTime));
     Map<String, String> defendParams = OrderPageBO.getDefendsParam();
     defendStatus = StringUtils.isEmpty(defendParams.get("DEFENSE_ENABLED")) ? DEFENSE_ENABLED
       : defendParams.get("DEFENSE_ENABLED");

     log.info("重新載入防禦參數:"+ defendParams);
     
     try {
      limitVisitTime = Integer.parseInt(defendParams.get("DEFENSE_MAX_ACCESS_TIMES"));
     } catch (NumberFormatException e) {
      limitVisitTime = DEFAULT_DEFENSE_MAX_ACCESS_TIMES;
     }

     try {
      measurementTimeInSeconds = Integer.parseInt(defendParams.get("DEFENSE_MEASURE_TIME")) * 60; // 單位是分鐘
     } catch (NumberFormatException e) {
      measurementTimeInSeconds = DEFAULT_MEASUREMENT_TIME_IN_SECONDS;
     }

     try {
      blockDurationTimeInSeconds = Integer.parseInt(defendParams.get("DEFENSE_BLOCK_TIME")) * 60; // 單位是分鐘
     } catch (NumberFormatException e) {
      blockDurationTimeInSeconds = DEFAULT_BLOCK_DURATION_TIME_IN_SECONDS;
     }

     //lastLoadingParamsTime = nextLoadingParamsTime;
     
    }finally{
     lock.writeLock().unlock();
    }
    
   }/*else{
    if (nextReloadTime == null){
     try{
      lock.writeLock().lock();
      nextReloadTime = DateUtil.formatDate(new Date(nextLoadingParamsTime), "yyyy-MM-dd HH:mm:ss");
     }finally{
      lock.writeLock().unlock();
     }
    }
   }*/
  }

 }
}

沒有留言:

張貼留言