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, architecture, core capabilities such as black‑white list, rate limiting, online configuration, detailed Java code, front‑end console, deployment, and practical use cases.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
How to Build a Lightweight Spring Boot API Firewall for Real‑Time Protection

1. Background and Pain Points

When developing backend services, we often encounter problems such as malicious traffic flooding an interface, lack of fine‑grained protection, hard‑coded risk policies, and chaotic black‑white list management.

Interface malicious traffic: a query interface is called massively in a short time, exhausting DB connections and crashing the service.

Lack of fine‑grained protection: only coarse global rate limiting exists; high‑value APIs (order, payment, export) have no dedicated protection.

Risk policies hard to change: rules are hard‑coded; each change requires code modification, packaging and deployment, raising ops cost.

Black‑white list chaos: temporary block rules are scattered in Nginx or database without a unified entry for developers, ops and testers.

Many enterprises introduce API gateways such as Kong, Spring Cloud Gateway, or Nginx+Lua, but these solutions are often too heavyweight:

Additional component increases deployment complexity.

High learning and usage cost, unsuitable for small teams.

Overkill for single‑machine or internal small systems.

Therefore a very practical demand emerges: embed a lightweight API firewall directly in a Spring Boot application to achieve rate limiting, risk control and online configuration without external gateways.

图片
图片
图片
图片
图片
图片

2. Design Idea

Goal: embed a protection layer in Spring Boot that performs a “pre‑check” for every API request.

Core capabilities:

Black‑white list: support IP whitelist (higher priority) and blacklist.

Rate‑limiting strategies:

QPS limit (e.g., each interface no more than 100 requests per second).

Time‑window limit (e.g., a single user no more than 60 calls per minute).

Burst control to avoid short‑term spikes.

Risk control rules: time‑window based access frequency, e.g., “single IP may access at most 10 times within 60 seconds”.

Online configuration: a front‑end console to modify rules in real time without restart.

Low intrusion: integrate as Interceptor or Filter without affecting existing business logic.

Architecture diagram:

┌────────────────────┐
 │   API Firewall     │ ← Intercept layer (black‑white list / rate‑limit / risk)
 └────────────────────┘
          ↓
 ┌────────────────────┐
 │   Application API  │
 └────────────────────┘

3. Technology Selection

Spring Boot Interceptor: best for request pre‑validation with low intrusion.

Guava Cache (or Caffeine): high‑performance in‑memory cache with expiration and concurrency support.

AtomicInteger + time window: simple and efficient counter‑based rate limiting.

Spring Boot Actuator: provides health check and monitoring endpoints.

Front‑end UI with Tailwind CSS + Chart.js: modern responsive management page.

For distributed scenarios, Redis + Lua can be added later; the current focus is single‑machine lightweight protection.

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 path pattern */
    private String apiPattern;
    /** QPS limit */
    private Integer qpsLimit;
    /** per‑user limit within time window */
    private Integer userLimit;
    /** time window (seconds) */
    private Integer timeWindow;
    /** enabled flag */
    private Boolean enabled;
    /** description */
    private String description;
    /** creation time */
    private LocalDateTime createdTime;
    /** update time */
    private LocalDateTime updatedTime;

    /** check if API path matches this rule */
    public boolean matches(String apiPath) {
        if (apiPattern == null || apiPath == null) {
            return false;
        }
        // support wildcard matching
        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, loaded rules: {}, blacklist: {}, whitelist: {}",
                ruleCache.size(), blacklistCache.size(), whitelistCache.size());
    }

    public FirewallRule getMatchingRule(String apiPath) {
        if (apiPath == null) {
            return null;
        }
        // exact match first
        FirewallRule exactMatch = ruleCache.get(apiPath);
        if (exactMatch != null && exactMatch.isEffectiveEnabled()) {
            return exactMatch;
        }
        // pattern match
        for (FirewallRule rule : ruleCache.values()) {
            if (rule.isEffectiveEnabled() && rule.matches(apiPath)) {
                return rule;
            }
        }
        return null;
    }

    public boolean isBlacklisted(String ipAddress) {
        if (ipAddress == null) {
            return false;
        }
        for (FirewallBlacklist blacklist : blacklistCache.values()) {
            if (blacklist.isValid() && blacklist.matches(ipAddress)) {
                return true;
            }
        }
        return false;
    }

    public boolean isWhitelisted(String ipAddress) {
        if (ipAddress == null) {
            return false;
        }
        for (FirewallWhitelist whitelist : whitelistCache.values()) {
            if (whitelist.isValid() && whitelist.matches(ipAddress)) {
                return true;
            }
        }
        return false;
    }

    @Scheduled(fixedRate = 300000) // every 5 minutes
    public void scheduledRefresh() {
        try {
            refreshRules();
            refreshBlacklist();
            refreshWhitelist();
            log.debug("Scheduled firewall rule cache refresh completed");
        } catch (Exception e) {
            log.error("Scheduled firewall rule cache refresh failed", e);
        }
    }
    // other CRUD methods omitted for brevity
}

4.3 Interceptor Logic

@Slf4j
@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 limit 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 ipAddress = getClientIpAddress(request);
        String apiPath = request.getRequestURI();
        String userAgent = request.getHeader("User-Agent");
        String method = request.getMethod();

        log.debug("Firewall check: IP={}, API={}, Method={}", ipAddress, apiPath, method);
        try {
            // whitelist
            if (ruleManager.isWhitelisted(ipAddress)) {
                log.debug("IP {} in whitelist, allow", ipAddress);
                logAccess(ipAddress, apiPath, userAgent, method, 200, null, startTime);
                return true;
            }
            // blacklist
            if (ruleManager.isBlacklisted(ipAddress)) {
                log.warn("IP {} in blacklist, reject", ipAddress);
                blockRequest(response, "IP address is blacklisted", 403);
                logAccess(ipAddress, apiPath, userAgent, method, 403, "IP blacklist blocked", startTime);
                return false;
            }
            // rule
            FirewallRule rule = ruleManager.getMatchingRule(apiPath);
            if (rule == null) {
                rule = createDefaultRule(apiPath);
            }
            // QPS check
            if (!checkQpsLimit(ipAddress, apiPath, rule)) {
                log.warn("IP {} exceeds QPS limit {} on {}", ipAddress, rule.getEffectiveQpsLimit(), apiPath);
                blockRequest(response, "Too many requests, please try later", 429);
                logAccess(ipAddress, apiPath, userAgent, method, 429, "QPS blocked", startTime);
                return false;
            }
            // user limit check
            if (!checkUserLimit(ipAddress, rule)) {
                log.warn("IP {} exceeds user limit {}", ipAddress, rule.getEffectiveUserLimit());
                blockRequest(response, "Request count exceeded, please try later", 429);
                logAccess(ipAddress, apiPath, userAgent, method, 429, "User limit blocked", startTime);
                return false;
            }
            // normal access
            logAccess(ipAddress, apiPath, userAgent, method, 200, null, startTime);
            log.debug("IP {} passed firewall check for {}", ipAddress, apiPath);
            return true;
        } catch (Exception e) {
            log.error("Firewall interceptor exception: IP={}, API={}", ipAddress, apiPath, e);
            logAccess(ipAddress, apiPath, userAgent, method, 500, "System error", startTime);
            return true; // allow to avoid service disruption
        }
    }

    private boolean checkQpsLimit(String ip, String api, FirewallRule rule) {
        int qpsLimit = rule.getEffectiveQpsLimit();
        if (qpsLimit <= 0) return true;
        String key = ip + ":" + api;
        AtomicInteger counter = qpsCache.getIfPresent(key);
        if (counter == null) {
            counter = new AtomicInteger(0);
            qpsCache.put(key, counter);
        }
        return counter.incrementAndGet() <= qpsLimit;
    }

    private boolean checkUserLimit(String ip, FirewallRule rule) {
        int userLimit = rule.getEffectiveUserLimit();
        if (userLimit <= 0) return true;
        AtomicInteger counter = userCache.getIfPresent(ip);
        if (counter == null) {
            counter = new AtomicInteger(0);
            userCache.put(ip, counter);
        }
        return counter.incrementAndGet() <= userLimit;
    }

    private String getClientIpAddress(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) {
            return xForwardedFor.split(",")[0].trim();
        }
        String xRealIp = request.getHeader("X-Real-IP");
        if (xRealIp != null && !xRealIp.isEmpty() && !"unknown".equalsIgnoreCase(xRealIp)) {
            return xRealIp;
        }
        return request.getRemoteAddr();
    }

    // blockRequest, logAccess, createDefaultRule omitted for brevity
}

4.4 Online Configuration API

@Slf4j
@RestController
@RequestMapping("/api/firewall")
@CrossOrigin(origins = "*")
public class FirewallController {
    @Autowired
    private RuleManager ruleManager;
    @Autowired
    private FirewallService firewallService;
    @Autowired
    private FirewallInterceptor firewallInterceptor;

    @GetMapping("/rules")
    public ResponseEntity<List<FirewallRule>> getRules() {
        try {
            List<FirewallRule> rules = ruleManager.getAllRules();
            return ResponseEntity.ok(rules);
        } catch (Exception e) {
            log.error("Failed to get firewall 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("Failed to save firewall rule", e);
            result.put("success", false);
            result.put("message", "System error: " + e.getMessage());
            return ResponseEntity.status(500).body(result);
        }
    }

    @DeleteMapping("/rules/{id}")
    public ResponseEntity<Map<String, Object>> deleteRule(@PathVariable Long id) {
        Map<String, Object> result = new HashMap<>();
        try {
            boolean success = ruleManager.deleteRule(id);
            if (success) {
                result.put("success", true);
                result.put("message", "Rule deleted successfully");
                return ResponseEntity.ok(result);
            } else {
                result.put("success", false);
                result.put("message", "Rule delete failed");
                return ResponseEntity.status(500).body(result);
            }
        } catch (Exception e) {
            log.error("Failed to delete firewall rule", e);
            result.put("success", false);
            result.put("message", "System error: " + e.getMessage());
            return ResponseEntity.status(500).body(result);
        }
    }

    // other endpoints for blacklist, logs, statistics omitted for brevity
}

5. Front‑End Console

A modern management page built with HTML5, Tailwind CSS and JavaScript provides full firewall management functions.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Web防火墙控制台</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 and dashboard omitted for brevity -->
</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
    password:
  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.ico

7. Practical Scenarios

7.1 Small‑to‑medium System Protection

e‑commerce order interface:

- ruleName: "订单保护"
  apiPattern: "/api/orders/**"
  qpsLimit: 50
  userLimit: 5
  timeWindow: 60
  enabled: true
  description: "防止恶意刷单和重复下单"

Content platform comment/like interfaces:

- ruleName: "内容互动限制"
  apiPattern: "/api/comments/**,/api/likes/**"
  qpsLimit: 200
  userLimit: 20
  timeWindow: 300
  enabled: true
  description: "防止垃圾评论和刷赞行为"

7.2 Burst Traffic Handling

During a flash‑sale, dynamically add a rule:

POST /api/firewall/rules
{
  "ruleName": "秒杀活动保护",
  "apiPattern": "/api/seckill/**",
  "qpsLimit": 1000,
  "userLimit": 1,
  "timeWindow": 60,
  "enabled": true,
  "description": "秒杀活动期间特殊限制"
}

Manual blacklist example:

FirewallBlacklist blacklist = new FirewallBlacklist();
blacklist.setIpAddress("192.168.1.100");
blacklist.setReason("恶意爬虫行为");
blacklist.setExpireTime(LocalDateTime.now().plusHours(24));
blacklist.setEnabled(true);
ruleManager.addBlacklist(blacklist);

7.3 Deployment and Operations

Docker image:

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 integration:

management:
  metrics:
    tags:
      application: firewall
  export:
    prometheus:
      enabled: true

Conclusion

This article implements a lightweight API firewall based on Spring Boot that provides real‑time protection through interceptor mechanisms, Guava Cache for high‑performance in‑memory rate limiting, and fine‑grained black‑white list management. A modern web console enables online rule configuration and immediate effect, offering a concise and efficient solution for small‑to‑medium projects.

microservicesBlacklistspring-bootapi-firewallrate-limiting
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.