必须在数据访问层用参数化堵死所有${}入口,网关层sql检测仅是辅助;重点检查mybatis中${}拼接、querywrapper.apply()及@selectprovider字符串拼接等高危点。

Spring Cloud Gateway 本身不解析 SQL,也不拼接数据库语句——它根本不会碰 SELECT、WHERE 或 IN。所谓“网关层的 SQL 注入风险”,其实是误把网关当成了防护兜底层,而真正该拦住的地方早被绕过了。
真正的风险点从来不在网关,而在后端服务里那些用 ${} 拼接表名、排序字段、动态条件的 MyBatis 语句,或 QueryWrapper.apply()、@SelectProvider 返回的字符串 SQL。网关能做的,只是在请求进入系统前做一层粗筛,它拦不住编码绕过、JSON body 混淆、或服务间调用中带毒的数据流转。
所以结论很直接:**不要指望靠网关过滤来修复 SQL 注入,它只能辅助;必须在数据访问层用参数化堵死所有 ${} 入口**。
为什么网关层的 SQL 检测容易失效
很多团队在 GlobalFilter 里写正则匹配 UNION SELECT、1=1、OR '1'='1,但这类黑名单策略在生产环境几乎必然漏报:
- URL 编码绕过:
%27%20OR%201%3D1不会匹配明文规则 - 注释混淆:
/**/UNION/**/SELECT或SELECT/*abc*/FROM躲开关键词扫描 - 大小写/空格变形:
SeLeCt、UNION%09SELECT(Tab 分隔) - JSON body 未缓存:没配
CacheRequestBodyGlobalFilter时,ServerWebExchange.getRequestBody()只能读一次,后续 filter 读不到,检测逻辑直接失效 - Header/Cookie 里的恶意字段常被忽略,比如
X-User-ID: admin' OR '1'='1
如果仍要在网关加 SQL 特征扫描,必须绕过 DataBuffer 内存陷阱
想读取并检查请求体(尤其是 POST),得用 DataBufferUtils.join() 合并流,但 WebFlux 的 DataBuffer 是 Netty 直接内存,不手动释放就会触发 OutOfDirectMemoryError。常见错误写法是读完就丢,没调 DataBufferUtils.release()。
正确做法是:
- 用
exchange.getRequest().getBody()获取原始流 - 调用
DataBufferUtils.join(...).flatMap(...)合并后转成byte[] -
立刻 调用
DataBufferUtils.release(buffer)释放原始DataBuffer - 用
ServerHttpRequestDecorator包装新 request,body 用bufferFactory().wrap(byte[])构造堆内 buffer(UnpooledHeapByteBuf),避免 Netty 直接内存泄漏
真正该盯死的三个后端代码位置
SQL 注入不是“有没有网关拦截”的问题,而是“哪一行代码把用户输入喂给了 SQL 解析器”。重点扫这些地方:
- 所有含
${tableName}、${orderBy}、${condition}的 MyBatis@Select或XML <script></script>标签——必须加白名单校验,比如if (!ALLOWED_TABLES.contains(tableName)) throw new IllegalArgumentException(); -
QueryWrapper.apply("status = {0}", userStatus)看似安全,但如果{0}是拼接进字符串的,实际等价于${};应改用eq("status", userStatus) -
@SelectProvider方法返回的是字符串而非SqlSource对象,或方法里用了String.format()/+拼接 SQL 片段——一律重构为Script类 +<if></if>标签
#{username},一边又在同一个 Mapper 里偷偷加了一行 ${dynamicField} ——这个 ${} 就是那个没被测试覆盖、上线后才暴雷的单点故障。











