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.

macrozheng
macrozheng
macrozheng
How to Build a Lightweight Spring Boot API Firewall for Real‑Time Protection

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.ico

7. 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: true

Conclusion

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.

microservicesInterceptorrate limitingBlacklistapi-firewall
macrozheng
Written by

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.

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.