不只是限流:在 Spring Boot 內嵌輕量級 API 防火墻,實現單機安全、動態黑名單與在線熱配置
作者:編程疏影
雖然引入 API 網關(如 Kong、Spring Cloud Gateway、Nginx+Lua)可以解決部分問題,但對于中小團隊來說,成本過高,維護復雜。
背景與動機
在后端開發中,很多團隊都會遇到以下問題:
- 接口被惡意調用:某些敏感接口(訂單查詢、報表導出)被高頻請求,數據庫連接耗盡,導致服務雪崩。
- 缺乏差異化防護:全局限流無法針對關鍵接口單獨保護。
- 規則更新不靈活:規則寫死在代碼中,每次修改都要重啟應用。
- 黑白名單分散:部分在 Nginx,部分在數據庫,缺乏統一管理。
雖然引入 API 網關(如 Kong、Spring Cloud Gateway、Nginx+Lua)可以解決部分問題,但對于中小團隊來說,成本過高,維護復雜。
因此,我們提出一種 內嵌在 Spring Boot 的輕量級 API 防火墻,支持:
- 黑白名單
- 限流與風控規則
- 動態黑名單(自動封禁惡意 IP)
- 在線管理(前端控制臺)
系統設計
功能清單
- 黑白名單
- 靜態配置(手動添加)
- 動態黑名單(違規 IP 自動封禁,支持 TTL 過期自動解封)
- 限流與風控
- QPS 限流
- 時間窗限流
- 單用戶限制
- 在線管理
- 規則管理
- 黑名單管理(手動 + 動態)
- 日志查看
核心實現
以下為關鍵模塊,包路徑統一采用 Linux 風格,前綴為 com.icoderoad。
動態黑名單實體
package com.icoderoad.firewall.model;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DynamicBlacklistEntry {
private String ip;
private LocalDateTime blockedAt;
private LocalDateTime expireAt;
public boolean isExpired() {
return LocalDateTime.now().isAfter(expireAt);
}
}RuleManager 擴展
package com.icoderoad.firewall.core;
@Slf4j
@Service
public class RuleManager {
private final Map<String, FirewallRule> ruleCache = new ConcurrentHashMap<>();
private final Map<String, FirewallBlacklist> blacklistCache = new ConcurrentHashMap<>();
private final Map<String, FirewallWhitelist> whitelistCache = new ConcurrentHashMap<>();
private final Map<String, DynamicBlacklistEntry> dynamicBlacklist = new ConcurrentHashMap<>();
// 添加到動態黑名單
public void blockIp(String ip, int minutes) {
DynamicBlacklistEntry entry = new DynamicBlacklistEntry(
ip,
LocalDateTime.now(),
LocalDateTime.now().plusMinutes(minutes)
);
dynamicBlacklist.put(ip, entry);
log.warn("IP {} 已被動態拉黑 {} 分鐘", ip, minutes);
}
// 檢查是否在黑名單
public boolean isBlacklisted(String ip) {
if (blacklistCache.containsKey(ip)) return true;
DynamicBlacklistEntry entry = dynamicBlacklist.get(ip);
if (entry == null) return false;
if (entry.isExpired()) {
dynamicBlacklist.remove(ip);
return false;
}
return true;
}
// 管理接口調用
public List<DynamicBlacklistEntry> getDynamicBlacklist() {
return new ArrayList<>(dynamicBlacklist.values());
}
public void removeDynamicBlacklist(String ip) {
dynamicBlacklist.remove(ip);
}
}攔截器擴展
package com.icoderoad.firewall.interceptor;
@Slf4j
@Component
public class FirewallInterceptor implements HandlerInterceptor {
@Autowired
private RuleManager ruleManager;
private final Cache<String, AtomicInteger> qpsCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
private final Cache<String, AtomicInteger> userCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
String ip = getClientIp(req);
String path = req.getRequestURI();
if (ruleManager.isWhitelisted(ip)) return true;
if (ruleManager.isBlacklisted(ip)) {
resp.sendError(403, "IP已被封禁");
return false;
}
FirewallRule rule = ruleManager.getMatchingRule(path);
if (!checkQps(ip, path, rule)) {
ruleManager.blockIp(ip, 5); // 動態拉黑5分鐘
resp.sendError(429, "訪問過于頻繁,已被臨時封禁");
return false;
}
if (!checkUser(ip, rule)) {
ruleManager.blockIp(ip, 10); // 動態拉黑10分鐘
resp.sendError(429, "請求次數超限,已被臨時封禁");
return false;
}
return true;
}
}管理接口擴展
package com.icoderoad.firewall.controller;
@Slf4j
@RestController
@RequestMapping("/api/firewall")
public class FirewallController {
@Autowired
private RuleManager ruleManager;
@GetMapping("/dynamic-blacklist")
public List<DynamicBlacklistEntry> getDynamicBlacklist() {
return ruleManager.getDynamicBlacklist();
}
@DeleteMapping("/dynamic-blacklist/{ip}")
public ResponseEntity<?> removeFromDynamicBlacklist(@PathVariable String ip) {
ruleManager.removeDynamicBlacklist(ip);
return ResponseEntity.ok("已移除動態黑名單:" + ip);
}
}前端管理控制臺
完整的前端代碼,使用 Tailwind CSS + Vanilla JS,支持:
- 查看規則
- 新增/刪除規則
- 查看黑名單(靜態 + 動態)
- 手動移除動態黑名單
- 日志輸出
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>API 防火墻控制臺</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50">
<div class="p-6">
<h1 class="text-2xl font-bold mb-6">API 防火墻管理</h1>
<!-- 動態黑名單 -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-2">動態黑名單</h2>
<table class="min-w-full bg-white shadow rounded">
<thead>
<tr class="bg-gray-100">
<th class="px-4 py-2">IP</th>
<th class="px-4 py-2">封禁時間</th>
<th class="px-4 py-2">過期時間</th>
<th class="px-4 py-2">操作</th>
</tr>
</thead>
<tbody id="dynamicBlacklistTable"></tbody>
</table>
</div>
<!-- 防火墻規則 -->
<div>
<h2 class="text-xl font-semibold mb-2">規則管理</h2>
<table class="min-w-full bg-white shadow rounded">
<thead>
<tr class="bg-gray-100">
<th class="px-4 py-2">規則名</th>
<th class="px-4 py-2">API Pattern</th>
<th class="px-4 py-2">QPS限制</th>
<th class="px-4 py-2">狀態</th>
</tr>
</thead>
<tbody id="rulesTable"></tbody>
</table>
</div>
</div>
<script>
async function loadDynamicBlacklist() {
const resp = await fetch('/api/firewall/dynamic-blacklist');
const list = await resp.json();
const tbody = document.getElementById('dynamicBlacklistTable');
tbody.innerHTML = '';
list.forEach(item => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="border px-4 py-2">${item.ip}</td>
<td class="border px-4 py-2">${item.blockedAt}</td>
<td class="border px-4 py-2">${item.expireAt}</td>
<td class="border px-4 py-2">
<button class="bg-red-500 text-white px-2 py-1 rounded" onclick="removeBlacklist('${item.ip}')">移除</button>
</td>
`;
tbody.appendChild(tr);
});
}
async function removeBlacklist(ip) {
await fetch('/api/firewall/dynamic-blacklist/' + ip, { method: 'DELETE' });
loadDynamicBlacklist();
}
async function loadRules() {
const resp = await fetch('/api/firewall/rules');
const list = await resp.json();
const tbody = document.getElementById('rulesTable');
tbody.innerHTML = '';
list.forEach(rule => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="border px-4 py-2">${rule.ruleName}</td>
<td class="border px-4 py-2">${rule.apiPattern}</td>
<td class="border px-4 py-2">${rule.qpsLimit || '-'}</td>
<td class="border px-4 py-2">${rule.enabled ? '啟用' : '禁用'}</td>
`;
tbody.appendChild(tr);
});
}
loadDynamicBlacklist();
loadRules();
</script>
</body>
</html>總結
這套方案在 Spring Boot 內嵌 API 防火墻 的基礎上,實現了 動態黑名單功能:
- 當 IP 請求頻繁觸發限流或違規時,會被自動拉黑一段時間
- 黑名單可在管理頁面中查看,并支持手動解封
- 黑名單 TTL 到期后自動解封,避免誤傷正常用戶
整體方案具備:
- 低成本:無需外部網關,內嵌即可運行
- 在線熱更新:規則和黑名單實時生效
- 風控增強:動態黑名單機制讓防護更智能
- 可擴展:未來可升級為分布式 Redis 版本
責任編輯:武曉燕
來源:
路條編程
























