MyBatis-Plus 的 orderByAsc/orderByDesc 不能直传前端参数,因字段名属SQL标识符,预编译无法防护注入;必须用白名单校验(如 Set.of("create_time","status")),在Controller层严格验证,否则易遭SQL注入。

MyBatis-Plus 的 orderByAsc 和 orderByDesc 为什么不能直接传前端参数?
因为这两个方法底层拼的是 SQL 的 ORDER BY 子句,而字段名属于标识符(identifier),不是值(value)。预编译参数绑定(?)对它完全无效——数据库协议不允许把列名当参数传。你写 queryWrapper.orderByAsc(request.getSortField()),框架就会原样把字符串拼进 SQL,比如变成 ORDER BY ${userInput}。攻击者传 id, (SELECT password FROM users LIMIT 1) 或 name ASC, email DESC,SQL 结构就变了。
用白名单校验排序字段比转义更可靠
别指望正则过滤或黑名单拦截,绕过太容易。必须用白名单硬控制可排序字段范围:
- 定义允许的字段集合,如
Set.of("create_time", "status", "amount") - 从请求中取
sortField后,先contains判断是否在白名单内,不在就直接返回 400 - 白名单应与数据库实际字段严格一致,区分大小写,不包含函数、别名、点号(如
u.name) - 如果需要支持多字段排序(如
create_time,amount),要拆分后逐个校验,且限制总字段数 ≤ 2
OrderItem 安全模式不是开箱即用的自动防护
MyBatis-Plus 的 OrderItem 安全模式默认是关闭的,而且它只对 QueryWrapper 构造时显式调用 addOrder 才生效,不覆盖 orderByAsc 这类快捷方法。启用方式是:
- 配置类中注册
PaginationInnerInterceptor时传入new MybatisPlusProperties().setOrderByChecker(...) - 或手动构建
OrderItem:用new OrderItem("create_time", true),再调queryWrapper.addOrder(orderItem) - 注意:即使开了安全模式,它默认也只是记录日志或抛异常,不会自动 fallback 到默认字段——你要自己处理校验失败逻辑
LIMIT/OFFSET 和排序字段耦合时风险加倍
常见错误写法:ORDER BY ${sortField} LIMIT #{size} OFFSET #{offset}。这里 ${sortField} 是致命漏洞点,哪怕 #{size} 和 #{offset} 走了预编译也无济于事。更危险的是,攻击者可在 sortField 中注入子查询,比如:
id, (SELECT COUNT(*) FROM information_schema.tables)
这种写法会直接执行子查询并返回结果到排序字段位置,可能泄露元数据。所以排序字段校验必须独立、前置,且不能依赖任何其他参数的“已安全”假设。
白名单校验必须在 Controller 层完成,不能拖到 Service;一旦放行,后续所有环节都默认该字段可信——这个信任边界划得越早、越窄,越不容易被绕过。

















