实现思路:
- 使用两个
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)或中间件以支持多实例。