order by后必须用${}而非#{},因jdbc预编译不支持列名、关键字等sql结构参数化;直接使用${sortfield}接收用户输入会导致sql注入,须在java层通过白名单严格校验字段及排序方向。

不能直接用 ${sortField} 接收用户输入的排序字段 —— 这是高危操作,MyBatis 3.5.x 不会帮你校验或转义它,拼进去就是 SQL 注入入口。
为什么 ORDER BY 后必须用 ${}?
因为 JDBC 的 PreparedStatement 不支持对列名、表名、关键字(如 ASC/DESC)做参数绑定。数据库在预编译阶段就需要知道这些结构信息,#{} 只能填值,填不了标识符。
-
SELECT * FROM user ORDER BY ?→ 报错:JDBC 不允许占位符出现在 ORDER BY 子句中 -
SELECT * FROM user ORDER BY #{sortField}→ 实际生成ORDER BY 'username'(带引号,语法错误) - 只有
${sortField}能生成ORDER BY username这样的合法 SQL
白名单校验必须在 Java 层完成
MyBatis 本身不提供字段白名单机制,校验逻辑必须由你写在 service 或 controller 中。XML 或注解里不做任何“信任”假设。
- 定义合法字段列表:
Set.of("username", "created_time", "status") - 校验失败立即抛
IllegalArgumentException,不要 fallback 到默认字段 - 避免正则模糊匹配(如
^[a-zA-Z_][a-zA-Z0-9_]*$),它无法阻止username ASC, (SELECT ...)这类组合注入 - 如果需支持升/降序,把完整选项作为白名单项:
"username ASC"、"created_time DESC",而不是分别校验字段和方向
XML 中使用 ${} 的安全写法
白名单校验通过后,才允许传入 XML;但 XML 本身仍需做最小化约束,比如禁止空值、限制长度、剔除空白字符。
- 不要在
@Select注解里用${}:注解字符串不可动态校验,风险更高 - XML 示例:
<select id="listUsers" resulttype="User"> SELECT * FROM user <where><if test="name != null">AND name = #{name}</if></where> ORDER BY ${safeSortField} <!-- 已经是校验后的字段 --> </select> - 确保传入的
safeSortField是校验后变量名,不是原始请求参数名 - 避免在同一个
<select></select>中混用未校验字段和已校验字段
最易被忽略的一点:白名单校验必须覆盖所有调用路径 —— controller、feign client、内部 RPC 入参,只要最终落到这个 SQL,就得过同一道校验。漏掉任意一个入口,防护就形同虚设。











