How to Build a Lightweight Spring Boot API Firewall for Real‑Time Protection
This article walks through the design and implementation of a lightweight API firewall embedded in a Spring Boot application, covering background pain points, core capabilities such as blacklist/whitelist, QPS and user‑limit controls, online configuration via a modern web console, technical choices, detailed code examples, deployment tips, and practical use cases for small‑to‑medium services.
1. Background and Pain Points
When developing backend services, common problems include malicious traffic flooding APIs, lack of fine‑grained protection, difficulty implementing risk control policies, and chaotic blacklist/whitelist management. Traditional API gateways such as Kong or Spring Cloud Gateway are heavyweight and increase deployment complexity for small‑scale projects.
2. Design Idea
Goal: embed a lightweight protection layer inside a Spring Boot application to perform pre‑checks on every API request.
Core capabilities :
Blacklist / whitelist support (IP whitelist has higher priority).
QPS, time‑window and burst traffic limiting.
Rule‑based risk control (e.g., “single IP may call at most 10 times in 60 seconds”).
Online configuration via a front‑end console, changes take effect immediately without restart.
Low intrusion using Interceptor or Filter without affecting existing business logic.
Architecture diagram:
┌────────────────────┐
│ API Firewall │ ← Intercept layer (blacklist / limit / risk)
└────────────────────┘
↓
┌────────────────────┐
│ Business API │
└────────────────────┘3. Technical Selection
Spring Boot Interceptor : best for request pre‑validation with low intrusion.
Guava Cache (or Caffeine): high‑performance in‑memory cache with expiration.
AtomicInteger + time window : simple counter‑based rate limiting.
Spring Boot Actuator : health check and monitoring endpoints.
Front‑end : Tailwind CSS + Chart.js for a modern responsive UI.
4. Core Implementation
4.1 Rule Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FirewallRule {
/** Primary key */
private Long id;
/** Rule name */
private String ruleName;
/** API pattern */
private String apiPattern;
/** QPS limit */
private Integer qpsLimit;
/** User limit per time window */
private Integer userLimit;
/** Time window in seconds */
private Integer timeWindow;
/** Whether enabled */
private Boolean enabled;
/** Description */
private String description;
/** Creation time */
private LocalDateTime createdTime;
/** Update time */
private LocalDateTime updatedTime;
/** Check if request path matches the rule */
public boolean matches(String apiPath) {
if (apiPattern == null || apiPath == null) {
return false;
}
String pattern = apiPattern.replace("**", ".*").replace("*", "[^/]*");
return apiPath.matches(pattern);
}
}4.2 Rule Manager
@Slf4j
@Service
public class RuleManager {
@Autowired
private FirewallRuleMapper ruleMapper;
@Autowired
private FirewallBlacklistMapper blacklistMapper;
@Autowired
private FirewallWhitelistMapper whitelistMapper;
/** Rule cache */
private final Map<String, FirewallRule> ruleCache = new ConcurrentHashMap<>();
/** Blacklist cache */
private final Map<String, FirewallBlacklist> blacklistCache = new ConcurrentHashMap<>();
/** Whitelist cache */
private final Map<String, FirewallWhitelist> whitelistCache = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
log.info("Initializing firewall rule manager...");
refreshRules();
refreshBlacklist();
refreshWhitelist();
log.info("Firewall rule manager initialized, rules: {}, blacklist: {}, whitelist: {}",
ruleCache.size(), blacklistCache.size(), whitelistCache.size());
}
// Methods for getMatchingRule, isBlacklisted, isWhitelisted, scheduled refresh, etc.
}4.3 Interceptor Logic
@Component
public class FirewallInterceptor implements HandlerInterceptor {
@Autowired
private RuleManager ruleManager;
@Autowired
private FirewallService firewallService;
@Value("${firewall.default.qps-limit:100}")
private int defaultQpsLimit;
@Value("${firewall.default.user-limit:1000}")
private int defaultUserLimit;
@Value("${firewall.default.time-window:60}")
private int defaultTimeWindow;
// QPS cache
private final Cache<String, AtomicInteger> qpsCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
// User limit cache
private final Cache<String, AtomicInteger> userCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
String ip = getClientIpAddress(request);
String api = request.getRequestURI();
String ua = request.getHeader("User-Agent");
String method = request.getMethod();
log.debug("Firewall check: IP={}, API={}, Method={}", ip, api, method);
try {
// 1. whitelist
if (ruleManager.isWhitelisted(ip)) {
log.debug("IP {} in whitelist, allow", ip);
logAccess(ip, api, ua, method, 200, null, startTime);
return true;
}
// 2. blacklist
if (ruleManager.isBlacklisted(ip)) {
log.warn("IP {} in blacklist, block", ip);
blockRequest(response, "IP address is blacklisted", 403);
logAccess(ip, api, ua, method, 403, "IP blacklist", startTime);
return false;
}
// 3. rule lookup
FirewallRule rule = ruleManager.getMatchingRule(api);
if (rule == null) {
rule = createDefaultRule(api);
}
// 4. QPS check
if (!checkQpsLimit(ip, api, rule)) {
log.warn("IP {} exceeds QPS limit {}", ip, rule.getEffectiveQpsLimit());
blockRequest(response, "Too many requests, please try later", 429);
logAccess(ip, api, ua, method, 429, "QPS limit", startTime);
return false;
}
// 5. User limit check
if (!checkUserLimit(ip, rule)) {
log.warn("IP {} exceeds user limit {}", ip, rule.getEffectiveUserLimit());
blockRequest(response, "Too many requests, please try later", 429);
logAccess(ip, api, ua, method, 429, "User limit", startTime);
return false;
}
// 6. normal access
logAccess(ip, api, ua, method, 200, null, startTime);
log.debug("IP {} passed firewall", ip);
return true;
} catch (Exception e) {
log.error("Firewall interceptor error: IP={}, API={}", ip, api, e);
logAccess(ip, api, ua, method, 500, "System error", startTime);
return true; // allow to avoid service disruption
}
}
// Helper methods: checkQpsLimit, checkUserLimit, getClientIpAddress, blockRequest, logAccess, createDefaultRule
}4.4 Online Configuration API
@RestController
@RequestMapping("/api/firewall")
@CrossOrigin(origins = "*")
public class FirewallController {
@Autowired
private RuleManager ruleManager;
@Autowired
private FirewallService firewallService;
@GetMapping("/rules")
public ResponseEntity<List<FirewallRule>> getRules() {
try {
return ResponseEntity.ok(ruleManager.getAllRules());
} catch (Exception e) {
log.error("Failed to get rules", e);
return ResponseEntity.status(500).build();
}
}
@PostMapping("/rules")
public ResponseEntity<Map<String, Object>> saveRule(@RequestBody FirewallRule rule) {
Map<String, Object> result = new HashMap<>();
try {
boolean success = ruleManager.saveRule(rule);
if (success) {
result.put("success", true);
result.put("message", "Rule saved successfully");
return ResponseEntity.ok(result);
} else {
result.put("success", false);
result.put("message", "Rule save failed");
return ResponseEntity.status(500).body(result);
}
} catch (Exception e) {
log.error("Save rule error", e);
result.put("success", false);
result.put("message", "System error: " + e.getMessage());
return ResponseEntity.status(500).body(result);
}
}
// Additional endpoints for blacklist, whitelist, logs, statistics, delete, etc. (omitted for brevity)
}5. Front‑end Console
A modern UI built with HTML5, Tailwind CSS and JavaScript provides dashboards, rule management, blacklist/whitelist tables, access logs and Chart.js visualizations.
<!-- Simplified HTML skeleton -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Web Firewall Console</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body class="bg-gray-50 font-sans">
<!-- Navigation, dashboard cards, charts, etc. -->
<script src="/js/app.js"></script>
</body>
</html>6. Configuration File (application.yml)
server:
port: 8080
servlet:
context-path: /
spring:
application:
name: springboot-firewall
jackson:
serialization:
write-dates-as-timestamps: false
time-zone: GMT+8
date-format: yyyy-MM-ddHH:mm:ss
datasource:
url: jdbc:h2:mem:firewall;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
driver-class-name: org.h2.Driver
username: sa
h2:
console:
enabled: true
path: /h2-console
settings:
web-allow-others: true
sql:
init:
mode: always
schema-locations: classpath:schema.sql
data-locations: classpath:data.sql
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.firewall.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
logging:
level:
com.example.firewall: DEBUG
org.springframework.web: INFO
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
firewall:
enabled: true
default-qps-limit: 100
default-user-limit: 60
cache-size: 1000
exclude-paths:
- /firewall/**
- /h2-console/**
- /actuator/**
- /static/**
- /favicon.ico7. Practical Scenarios
7.1 Small‑to‑Medium System Protection
E‑commerce order API : limit to 50 QPS and 5 requests per minute per IP to prevent flash‑sale abuse.
- ruleName: "Order Protection"
apiPattern: "/api/orders/**"
qpsLimit: 50
userLimit: 5
timeWindow: 60
enabled: true
description: "Prevent malicious order spamming"Content platform : restrict comment and like endpoints to curb spam.
- ruleName: "Content Interaction Limit"
apiPattern: "/api/comments/**,/api/likes/**"
qpsLimit: 200
userLimit: 20
timeWindow: 300
enabled: true
description: "Prevent spam comments and likes"7.2 Burst Traffic Handling
During a marketing campaign, a new rule can be added via the API to protect a seckill endpoint.
POST /api/firewall/rules
{
"ruleName": "Seckill Protection",
"apiPattern": "/api/seckill/**",
"qpsLimit": 1000,
"userLimit": 1,
"timeWindow": 60,
"enabled": true,
"description": "Special limits for flash‑sale"
}Manual blacklist example for a malicious crawler:
FirewallBlacklist blacklist = new FirewallBlacklist();
blacklist.setIpAddress("192.168.1.100");
blacklist.setReason("Malicious crawler");
blacklist.setExpireTime(LocalDateTime.now().plusHours(24));
blacklist.setEnabled(true);
ruleManager.addBlacklist(blacklist);7.3 Deployment & Operations
Dockerfile for containerised deployment:
FROM openjdk:11-jre-slim
VOLUME /tmp
COPY target/springboot-firewall-1.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]Prometheus metrics can be enabled via Spring Boot Actuator:
management:
metrics:
tags:
application: firewall
export:
prometheus:
enabled: trueConclusion
The article demonstrates a lightweight API firewall built on Spring Boot that provides real‑time protection through interceptor‑based request filtering, Guava‑based in‑memory caching, configurable QPS and user limits, blacklist/whitelist management, and a modern web console for online rule updates. The architecture is simple, low‑intrusive and suitable for quick integration into small‑to‑medium projects.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
