Web 應用常面臨惡意刷新、暴力遍歷、資料洩漏等威脅。硬體防火牆成本高、策略僵化;軟體層面的入侵檢測(IDS)可針對業務特徵精準攔截。本文基於 EasyJWeb 框架實戰代碼,展示一個基於 IP+用戶、時間窗口、閾值觸發的輕量級防刷新系統。
UserConnect - 封裝單用戶訪問狀態UserConnectManage - 多線程安全的檢測、記錄、清理邏輯public class UserConnect {
private String userName; // 用戶名/標識
private String ip; // 客戶端 IP
private Date firstFailureTime; // 首次違規時間(時間窗口起點)
private Date lastLoginTime; // 最近訪問時間
private int failureTimes; // 當前窗口內違規計數
private int status = 0; // 0=正常, -1=鎖定
// Getter/Setter 略
}
public class UserConnectManage {
// 靜態配置參數(可動態調整)
private static int maxFailureTimes = 10; // 最大允許違規次數
private static long maxFailureInterval = 10000; // 時間窗口:10 秒
private static long waitInterval = 60000; // 鎖定冷卻期:60 秒
private static int maxOnlineUser = 200; // 最大在線用戶數(防記憶體溢出)
private static final Map users = new HashMap<>(); // key: "ip:username"
private static Thread checkThread = null;
// 靜態內部類:後台定時清理線程(每 2 秒掃描)
private static class CheckTimeOut implements Runnable {
private final Thread parentThread;
public CheckTimeOut(Thread parent) { this.parentThread = parent; }
public void run() {
while (parentThread.isAlive()) {
try {
Thread.sleep(2000);
cleanExpiredEntries(); // 清理過期、超量條目
} catch (Exception e) { e.printStackTrace(); }
}
}
}
// 核心檢測入口:返回 true=放行, false=攔截
public static synchronized boolean checkLoginValidate(String ip, String userName) {
String key = ip + ":" + userName;
Date now = new Date();
UserConnect uc = users.get(key);
if (uc == null) { // 首次訪問
uc = new UserConnect();
uc.setIp(ip); uc.setUserName(userName);
uc.setFailureTimes(0); uc.setFirstFailureTime(now);
users.put(key, uc);
if (checkThread == null) new CheckTimeOut(Thread.currentThread());
return true;
}
// 核心判斷邏輯
if (uc.getFailureTimes() > maxFailureTimes) {
long elapsed = now.getTime() - uc.getFirstFailureTime().getTime();
if (elapsed < maxFailureInterval) { // 窗口內超閾值
uc.setStatus(-1); // 鎖定
return false;
} else if (uc.getStatus() == -1 && elapsed < (maxFailureInterval + waitInterval)) {
return false; // 冷卻期內持續攔截
} else {
// 窗口已過,重置計數器
uc.setFailureTimes(0);
uc.setFirstFailureTime(now);
uc.setStatus(0);
}
}
uc.setFailureTimes(uc.getFailureTimes() + 1); // 計數+1
return true;
}
// 清理策略:優先刪除過期,不足則強制刪除一半(保護記憶體)
private static void cleanExpiredEntries() {
// ... 遍歷 users,移除 firstFailureTime 超過 maxFailureInterval 的條目
// 若刪除 < 5 個且總數仍超 maxOnlineUser,強制刪除一半最舊條目
}
// 輔助方法:reset/remove/clear 供登出、管理員解鎖使用
// 配置 Getter/Setter 略
}
public class ActionServlet extends HttpServlet {
public void service(HttpServletRequest request, HttpServletResponse response) {
// 入侵檢測:第一道關卡
if (!UserConnectManage.checkLoginValidate(request.getRemoteAddr(), "guest")) {
String msg = "您對頁面的刷新太快,請等待" +
(UserConnectManage.getWaitInterval()/1000) + "秒後再刷新頁面!";
info(request, response, new Exception(msg)); // 友好錯誤頁
return; // 直接終止後續處理
}
// ... 正常業務分發流程
}
}
| 決策點 | 方案 | 理由 |
|---|---|---|
| 識別維度 | IP + 用戶名 | 兼顧匿名用戶(IP)與登入用戶(用戶名),防止代理 IP 誤傷 |
| 時間窗口 | 滑動窗口 | 以首次違規時間為起點,避免固定窗口邊界被繞過 |
| 鎖定機制 | 狀態機:正常→鎖定→冷卻→正常 | 防止瞬時高頻後立即恢復,給予懲罰期 |
| 記憶體保護 | 最大在線數 + 定時清理 + 強制淘汰 | 防止惡意構造大量 Key 耗盡 Heap |
| 併發安全 | 方法級 synchronized + 靜態 Map | 簡單有效,寫少讀多場景性能可接受 |
這套機制在 EasyJWeb 官網(bbs.easyjf.com)穩定運行多年,有效遏制了惡意刷新。核心啟示:入侵檢測不應是通用規則的堆砌,而應是針對具體業務場景(刷新頻率、登入失敗、資料遍歷)的精準建模。從這裡開始,構建你自己的業務安全防線。