MyBatis-Plus Wrapper 本身不防注入,安全依赖正确使用内置方法(如eq、like)、禁用字符串拼接、动态结构白名单校验;apply是唯一可导致注入的入口,字段名等动态内容必须白名单校验或用LambdaQueryWrapper锁定。

MyBatis-Plus 的 Wrapper 本身不防注入,安全完全取决于你传了什么、怎么传——用对内置方法(eq、like 等)+ 避开字符串拼接 + 白名单校验动态结构,才能守住底线。
为什么 eq/like 这些内置方法默认安全
它们底层走 JDBC PreparedStatement 参数绑定,和 MyBatis 的 #{} 本质一致:用户输入只作为参数值传入,不参与 SQL 解析。哪怕 userInput 是 "admin' OR '1'='1",最终执行的也只是 WHERE username = ?,? 被原样当做一个字符串值处理。
-
queryWrapper.like("name", keyword)安全,框架自动处理通配符与转义逻辑;别自己写"%" + keyword + "%",否则keyword含%或_会破坏语义 -
queryWrapper.in("id", idList)安全,内部用预编译批量参数,不是字符串拼接 - 所有
gt、between、isNotNull等内置方法同理,无需额外加SqlUtils.sqlInject()
apply 方法是唯一主动开后门的地方
apply 不走参数绑定,它直接把字符串塞进 SQL,是 Wrapper 里唯一能“亲手写注入”的入口。
- ❌ 危险:
wrapper.apply("status = '" + statusValue + "'")→ 若statusValue是"1'; TRUNCATE user; --",SQL 直接执行删表 - ✅ 安全:
wrapper.apply("status = {0}", statusValue)→{0}触发参数绑定,等价于? - ⚠️ 注意:
{0}只保护值,不能用于字段名、排序方向或表名。写wrapper.apply("ORDER BY {0}", sortField)会报错或被忽略
字段名和排序字段必须白名单校验
orderByAsc(sortParam)、groupBy("user_id") 这类方法,字段名是纯字符串拼接进 SQL 的,不走任何绑定机制,毫无防护能力。
- 前端传
sort=update_time; DROP TABLE user→ 后端直接wrapper.orderByAsc(sortParam)→ 生成ORDER BY update_time; DROP TABLE user - 必须显式校验:
if (!List.of("price", "create_time", "sales").contains(sortField)) { throw new IllegalArgumentException(); } - 更稳妥:改用
LambdaQueryWrapper,例如lambdaWrapper.orderByAsc(User::getCreateTime),字段名在编译期锁定,无法被外部污染
LambdaQueryWrapper 并不自动免疫,但锁死了字段名源头
LambdaQueryWrapper 的安全优势不在 Wrapper 类型本身,而在字段引用方式:用 User::getUsername 替代 "username" 字符串。这带来两个硬性保障:
- 编译期类型检查:字段不存在或拼错,直接编译失败,不会等到运行时报错或被篡改
- 运行时反射提取:字段名由 Java Bean 属性推导而来,不经过字符串解析,绕过所有“字段名来自用户输入”的路径
- ⚠️ 但
apply在LambdaQueryWrapper里照样危险——它不认你前面用了多少个User::getXXX,只看你传进去的 SQL 片段干不干净
真正容易被忽略的是:动态结构(如排序字段、分组字段、条件字段)一旦脱离编译期约束,就必须靠白名单兜底;而很多人以为用了 LambdaQueryWrapper 就一劳永逸,结果在 apply 或 orderByAsc 里悄悄放行了不可信字符串。

















