最有效的sql注入防御是在globalfilter中前置遍历参数并解码后关键词匹配:get需urldecoder解码query再逐value检测,post/json须递归解析所有字符串字段并同样解码检测,且必须校验timestamp(±5分钟)和nonce(redis去重)防重放。

直接在 GlobalFilter 里做参数遍历 + 关键词匹配是最有效、最前置的防御方式。它不依赖业务层是否用了 PreparedStatement,也不管你用的是 MyBatis 还是原生 JDBC —— 攻击载荷在网关就卡死,业务服务根本收不到恶意请求。
GET 请求参数怎么扫?别只看 query string 顶层
很多人只对 uri.getRawQuery() 做一次 contains 判断,但攻击者早把 ' OR 1=1-- 藏在 base64 编码或 URL 编码里了。必须先解码再检查:
- 用
URLDecoder.decode(rawQuery, StandardCharsets.UTF_8)解码原始 query 字符串 - 再调用工具类(如
SqlInjectionRuleUtils.getRequestSqlKeyWordsCheck())逐字段拆分key=value,对每个value单独检测 - 注意:不要用
uri.getQueryParams()直接拿 Map —— 它会自动解码一次,但若参数本身含双重编码(如%2527),就会漏掉 - 命中关键词(如
UNION、SELECT、;、--、#,忽略大小写)立即返回400,不要重写 URI 后再放行
POST/PUT 的 JSON Body 怎么递归扫描?不能只 parse 顶层
JSON 可能嵌套多层,比如 {"filter": {"name": "admin' -- "}}。如果只取 body.toString() 或只解析第一层,深层字符串字段就完全逃逸了。
- 必须用
exchange.getRequest().getBody()拿到Flux<databuffer></databuffer>,异步读取完整 body - 转成
String后用 Jackson 的JsonNode递归遍历所有JsonNodeType.STRING字段值 - 对每个字符串值做和 GET 参数相同的解码 + 关键词检查(同样要忽略大小写、支持空格绕过等常见变体)
- 别用
ObjectMapper.readTree()后直接toString()—— 那只是序列化结果,不是原始字段值,会漏掉结构化绕过
为什么必须校验 timestamp 和 nonce 才算真防重放?
光拦 SQL 关键词没用。攻击者可以截获一个合法带签名的请求,反复重放它 —— 网关若不验证时效性和唯一性,等于白拦。
-
timestamp必须是long类型,且与网关当前时间差 ≤ 300000ms(5 分钟),超时直接拒 -
nonce长度 ≥ 8,且需用 Redis 记录最近 5 分钟所有已用nonce,重复即拒(不能只用本地缓存) - 签名原文必须包含
timestamp和nonce,且二者按字典序拼接 —— 否则攻击者可固定一个nonce,只改timestamp尝试窗口 -
appSecret绝不能硬编码,必须通过appKey查配置中心动态获取,否则签名机制形同虚设
真正难的不是写匹配逻辑,而是处理边界:JSON 多层嵌套、双重 URL 编码、大小写混用、空格/制表符绕过、注释符号组合(--+、#、/* */)。这些细节不覆盖,规则就只是纸老虎。










