spring security 本身不提供 sql 防火墙能力,需手动将检测过滤器前置到链首并确保能安全读取原始请求体,否则因流已被消费或解析方式不匹配导致失效。

Spring Security 本身不提供 SQL 防火墙能力,也无法与数据库防火墙或网关层 WAF 自动协同;所谓“配合”,必须靠你手动把检测逻辑前置到 Spring Security 过滤链最前端,并确保它能真实读取原始请求体。
为什么 addFilterBefore() 常常不起作用
很多人在 UsernamePasswordAuthenticationFilter 前注册自定义 filter,就以为 SQL 检测已生效。但实际常失败,原因包括:
-
ContentCachingRequestWrapper或 Spring MVC 的参数解析器可能已提前消费了request.getInputStream(),导致你的 filter 再调用时抛IllegalStateException -
request.getParameterMap()对 JSON body 完全无效——{"name":"admin'--"}不会出现在参数里 - 仅靠
http.addFilterBefore()不保证执行顺序;必须用FilterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE)才真正排在最前
如何让 Spring Security 过滤链真正拿到原始请求体
关键不是加 filter,而是让 filter 能反复、安全地读 body。必须自己缓存并重读:
- 继承
HttpServletRequestWrapper,构造时用IOUtils.toByteArray(request.getInputStream())一次性读完原始流,保存为byte[] - 重写
getInputStream()返回new ByteArrayInputStream(cachedBytes) - 根据
Content-Type分支处理:是application/json就转字符串扫描;是application/x-www-form-urlencoded就 URL 解码后解析;否则跳过 - 正则匹配建议用
(?i)(select|union|exec|xp_|--|/\*|\*),但注意这仅是初筛,不能替代参数化查询
SQL 防火墙的边界在哪,哪些必须交给其他层
Spring Security 层的拦截有明确局限,超出部分必须由其他环节兜底:
- 它看不到 gRPC payload、Dubbo 泛化调用、或 JDBC 直连产生的 SQL——这些根本不会经过 Servlet 过滤链
- 无法识别十六进制编码(如
0x554e494f4e)、双写(SELSELECTECT)、或注释分隔(SEL/*bypass*/ECT)等绕过手法 - DAO 层必须强制使用
#{}(MyBatis)、命名参数(JPA)、PreparedStatement;字符串拼接"WHERE name = '" + name + "'"是硬性红线 - 数据库账号权限必须最小化:查用户接口只给
SELECT user.*,禁用DROP、EXECUTE、跨库权限
真正容易被忽略的是:SQL 防护不是“加个 filter 就完事”,而是要确认该 filter 是否真正在 DispatcherServlet 之前、是否真能拿到未解码的原始字节流、以及是否和业务层的参数校验(@Valid + @Pattern)形成闭环。缺一不可。











