惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

I
InfoQ
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
P
Privacy & Cybersecurity Law Blog
Cloudbric
Cloudbric
云风的 BLOG
云风的 BLOG
S
Secure Thoughts
Stack Overflow Blog
Stack Overflow Blog
G
Google Developers Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
SegmentFault 最新的问题
博客园 - Franky
T
Tenable Blog
T
The Blog of Author Tim Ferriss
博客园 - 三生石上(FineUI控件)
V
V2EX
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Troy Hunt's Blog
罗磊的独立博客
WordPress大学
WordPress大学
SecWiki News
SecWiki News
The Cloudflare Blog
S
Securelist
小众软件
小众软件
Schneier on Security
Schneier on Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - 叶小钗
Google Online Security Blog
Google Online Security Blog
Forbes - Security
Forbes - Security
阮一峰的网络日志
阮一峰的网络日志
量子位
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
W
WeLiveSecurity
A
Arctic Wolf
大猫的无限游戏
大猫的无限游戏
The Last Watchdog
The Last Watchdog
C
Cybersecurity and Infrastructure Security Agency CISA
宝玉的分享
宝玉的分享
MyScale Blog
MyScale Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 司徒正美
Vercel News
Vercel News
H
Help Net Security
Y
Y Combinator Blog
PCI Perspectives
PCI Perspectives
L
LINUX DO - 最新话题
H
Hackread – Cybersecurity News, Data Breaches, AI and More

博客园 - 华安

Unity 相机:正交 (Orthographic) vs 透视 (Perspective) - 华安 unity中的button中的onclick控制面板中四个参数的意思分别是什么 - 华安 微信小程序开发中触发 onshow的几种特殊情况 SQL Server备份心得 MYSQL 备份数据库 微信与支付支付功能开发 微信小程序中页面配置下拉刷新 unity中 相机没有视锥效果线框了,如何打开 JSONPath表达式 C# 中的操作JSON类 JObject cookie中的 HttpOnly 、Secure、SameSite 解释 Spring-boot 中基于 IP 的限流和自动封禁 Filter 登录 用 HMAC-SHA256 实现 TwoFA(二重验证)的坑 pixi-filters中的BackdropBlurFilter使用注意事项 PixiJS中的 SplitBitmapText.chars中的每个 BitmapText的x值分析 legend隐藏不想显示的图例 spring-boot HttpServletResponse response.sendRedirect是会跳转到 http而不是https springboot获取post请求参数 Javascript如何判断是触摸屏还是PC端 JavaScript获取鼠标点一个元素,获取鼠标点击的元素内的位置 spring-boot中配置Mongodbd的问题小结 Rest Template中添加 PATCH请求。 CSS实现修改CheckBox样式 SpringBoot+Thyemleaf报错:Error resolving template Template might not exist or might not be accessible div display flex 如何出现横向滚动条
利用Spring Boot的 filter 结合ConcurrentHashMap 实现“同一IP每分钟最多允许300个404请求,超出后禁用30分钟访问”
华安 · 2026-01-23 · via 博客园 - 华安

实现思路:

  • 使用两个ConcurrentHashMap
    • 一个存储每个IP的请求统计信息(请求数、404数、统计更新时间)
    • 一个存储被封禁的IP及封禁到期时间
  • 通过过滤器中的doFilter(),在请求前检查是否被封禁,在请求后检查响应状态码,统计404次数
  • 超过404阈值时封禁IP,直到30分钟后自动解封

代码示例

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

@WebFilter(urlPatterns = "/*")
public class RateLimitFilter implements Filter {

    private static class IpStats {
        int requestCount = 0;
        int error404Count = 0;
        long lastResetTime = System.currentTimeMillis();
    }

    // 存储IP的请求统计信息
    private final ConcurrentHashMap<String, IpStats> ipStatsMap = new ConcurrentHashMap<>();
    // 存储被封禁的IP及其解封时间
    private final ConcurrentHashMap<String, Long> blackListMap = new ConcurrentHashMap<>();

    // 配置参数
    private static final int MAX_REQUESTS_PER_MINUTE = 300;
    private static final int MAX_404_COUNT = 300;
    private static final long BLOCK_TIME_MILLIS = 30 * 60 * 1000; // 30分钟

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String ip = request.getRemoteAddr();
        long currentTime = System.currentTimeMillis();

        // 检查是否被封禁
        Long blockUntil = blackListMap.get(ip);
        if (blockUntil != null && blockUntil > currentTime) {
            ((HttpServletResponse) response).setStatus(429); // 太多请求
            response.getWriter().write("访问受限,请稍后重试。");
            return;
        } else if (blockUntil != null && blockUntil <= currentTime) {
            // 解封
            blackListMap.remove(ip);
        }

        // 统计请求信息
        IpStats stats = ipStatsMap.computeIfAbsent(ip, k -> new IpStats());

        // 存在跨分钟的情况,重置统计
        if (currentTime - stats.lastResetTime > 60 * 1000) {
            stats.requestCount = 0;
            stats.error404Count = 0;
            stats.lastResetTime = currentTime;
        }

        // 请求计数
        stats.requestCount++;
        ipStatsMap.put(ip, stats);

        // 处理请求
        // 使用自定义Response包装器捕获状态码
        StatusCaptureResponseWrapper wrappedResponse = new StatusCaptureResponseWrapper((HttpServletResponse) response);
        chain.doFilter(request, wrappedResponse);

        // 判断响应状态码
        if (wrappedResponse.getStatus() == 404) {
            stats.error404Count++;
        }

        // 超过404阈值,加入黑名单
        if (stats.error404Count >= MAX_404_COUNT) {
            blackListMap.put(ip, currentTime + BLOCK_TIME_MILLIS);
            // 可选: 清理统计数据
            ipStatsMap.remove(ip);
        }
    }

    /**
     * 自定义ResponseWrapper,用于捕获状态码
     */
    private static class StatusCaptureResponseWrapper extends HttpServletResponseWrapper {
        private int httpStatus = 200;

        public StatusCaptureResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        @Override
        public void setStatus(int sc) {
            super.setStatus(sc);
            this.httpStatus = sc;
        }

        @Override
        public void sendError(int sc) throws IOException {
            super.sendError(sc);
            this.httpStatus = sc;
        }

        @Override
        public void sendError(int sc, String msg) throws IOException {
            super.sendError(sc, msg);
            this.httpStatus = sc;
        }public int getStatus() {
            return this.httpStatus;
        }
    }
}

配置说明

  • 这个Filter需要添加到Spring Boot中,可以通过@WebFilter注解,或者在配置类中注册。
  • 请求到达时,先检查IP是否被封禁。
  • 请求结束后,根据响应状态码更新404计数。
  • 超过404阈值,会封禁IP30分钟。

小结

  • 这是一个纯Filter方案示例,适合单实例部署。
  • 生产环境建议结合缓存(如Redis)或中间件以支持多实例。