微服务中sql注入隐患源于跨服务调用时对参数的信任误判。需在服务入口显式类型转换与范围校验,禁用mybatis ${},动态字段用白名单控制,跨服务数据须重新验证,db权限最小化,禁止传递可执行内容,补偿逻辑也需严格参数化。

微服务架构下,SQL注入隐患往往不来自单个服务的 SQL 拼接,而是源于跨服务调用时对下游服务传入参数的信任误判——你认为 user_id 是上游服务校验过的数字,结果它其实是 "1 OR 1=1" 经过 JSON 序列化后传来的字符串。
别把 request.body 当成“已消毒”的输入
很多团队在网关或 API 层做了参数校验,就默认下游服务收到的 userId 字段一定是整型。但实际中:JSON 不强制类型、gRPC 的 int32 字段被前端伪造为字符串、Kafka 消息体是纯文本、OpenFeign 的 @RequestBody 未做反序列化后类型断言——这些都会让“看似安全”的参数落地成危险的字符串。
- 检查所有跨服务通信协议:HTTP(尤其是 POST/PUT 的 JSON)、gRPC(message 定义是否允许 string 替代 int)、消息队列(payload 是否经过 schema 验证)
- 在每个服务入口处对关键参数做显式类型转换和范围校验,例如 Java 中用
Long.parseLong()+try-catch NumberFormatException,而非直接绑定到Long字段(Spring Boot 默认宽松转换可能绕过校验) - 避免在 DAO 层直接拼接
WHERE id = ${userId}—— 即使上游声称“已校验”,你也得当它没校验过
MyBatis 的 ${} 和 #{} 不是风格选择,是安全开关
在微服务的持久层,${} 是把变量原样插入 SQL 字符串,等同于字符串拼接;#{} 才触发 PreparedStatement 参数绑定。只要用了 ${},哪怕参数来自内部 RPC 调用,也立刻退化为高危路径。
- 全局搜索项目中所有
${,重点审查:ORDER BY ${sortField}、IN (${idList})、TABLE_NAME = '${tableName}' - 动态表名、字段名、排序方向这类无法参数化的场景,必须用白名单控制:
if ("name".equals(sortField) || "created_at".equals(sortField)) { ... } - 批量 IN 查询不要硬拼
${idList},改用 MyBatis 的<foreach></foreach>标签配合#{},它会为每个元素生成独立占位符
服务间调用链路上的“信任边界”比你想象得更靠近下游
一个典型错误是:A 服务调用 B 服务获取用户数据,B 返回 {"id": "123", "name": "<script>alert(1)</script>"},A 服务直接拿这个 name 去拼 SQL 插入日志表——这里 XSS 和 SQL 注入风险同时存在,而根源是 A 服务把 B 的输出当成了“可信上下文”。
- 任何跨服务返回的数据,只要参与本地 SQL 构建,就必须重新走输入验证流程:长度、字符集、正则白名单(如用户名只允许
[a-zA-Z0-9_]) - 数据库连接池账号权限必须最小化:B 服务的 DB 用户不能有
DROP TABLE权限,A 服务的 DB 用户甚至不应能读取 B 服务的表 - 禁止在服务间传递原始 SQL 片段、表达式字符串、或可执行脚本内容——这类字段应统一标记为
@Sensitive并在序列化时脱敏或拒绝传输
最易被忽略的一点:分布式事务中,补偿逻辑(如 Saga 的回滚 SQL)往往写在注释里或配置文件中,没人审查它的参数绑定方式。只要有一处 delete from order where user_id = ${uid} 写在补偿脚本里,整个链路的安全防线就形同虚设。










