HandlerInterceptor无法拦截SQL注入载荷,因其preHandle()仅获取解析后的参数,无法触达原始JSON body、Header、Path Variable及文件字段;应改用@WebFilter在Servlet容器最前端统一检测getInputStream()等原始输入源。

Spring Security 本身不提供 SQL 字符拦截能力,也不该承担这个职责;强行在 SecurityFilterChain 中加过滤逻辑,大概率漏掉 JSON body、文件字段、Header 等入口,且易与 WAF 冲突。
为什么 HandlerInterceptor 无法拦截 SQL 注入载荷
HandlerInterceptor 的 preHandle() 方法拿到的是 Servlet 容器解析后的参数,比如 request.getParameter("id") —— 这意味着:
- 原始 POST JSON body(如
{"name":"admin' OR 1=1--"})早已被 Spring MVC 的HttpMessageConverter解析成对象,过滤器看到的只是反序列化后的字符串,特征已被“平滑” - multipart/form-data 中的文件字段、
request.getHeader("X-Api-Key")、Path Variable(如/user/{id})完全不可见 - 若请求体被多次读取(如
ContentCachingRequestWrapper缓存后又交由 Jackson 解析),部分老版本 WAF 可能拿不到原始流,导致双端都失效
应该用 @WebFilter 而非 SecurityFilterChain
真正能触达原始请求的,是 Servlet 规范层面的 @WebFilter。它在容器最前端运行,可统一处理 getInputStream()、getReader()、getPart() 三类输入源:
- 注册时必须加
@Component,否则嵌入式 Tomcat/Jetty 不会加载 - 顺序必须早于
SecurityFilterChain:用@Order(Ordered.HIGHEST_PRECEDENCE)或显式配置filterRegistrationBean.setOrder(1) - 白名单路径要排除:
/actuator/health、/swagger-ui.html、/css/等,否则健康检查失败或文档打不开 - 正则模式别硬编码,走配置项:
@Value("${security.sql-pattern:SELECT|INSERT|UPDATE|DELETE|UNION\s+SELECT|EXEC\s+|xp_}")
过滤器里怎么安全读取并检测原始 body
关键不是“替换”,而是“检测 + 拒绝”,避免误杀(比如用户昵称含 or 就拦)。示例核心逻辑:
String body = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
if (body != null && Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(body).find()) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.getWriter().write("{"error":"SQL injection detected"}");
return;
}
- 必须用
IOUtils.toString(...)或手动 BufferedReader 读取,不能调super.getParameterMap()—— 那是解析后结果 - 对
application/json类型才读 body;application/x-www-form-urlencoded仍需额外扫getParameterNames() - 不要用
replaceAll()清洗,清洗会破坏业务语义(如把order by name改成order by name),只做检测+阻断
和 WAF 协同时最容易忽略的点
很多团队加了过滤器却仍被绕过,问题常出在协同细节上:
- WAF 控制台里 SQL 注入规则动作必须是
拦截,不是日志或观察;否则攻击流量直接透传到你过滤器,压力全扛在应用层 - 确认 WAF 开启了 JSON 解析:否则
{"q":"1' AND SLEEP(5)--"}这种 payload,WAF 当纯文本放过,你的过滤器也只看到解析后的q="1' AND SLEEP(5)--",但已晚了一步 - 禁用任何无差别重写
HttpServletRequestWrapper的自定义 Filter(尤其不要在getParameter()里全局replaceAll),否则 WAF 日志中还原不出原始 payload,攻防复盘时查无实据











