mybatis 的 标签本身不防 sql 注入,仅用于绑定 ognl 表达式结果为变量;其安全性取决于后续引用方式:配合 #{}(预编译)则安全,配合 ${}(字符串替换)仍会注入。

MyBatis 的 <bind></bind> 标签本身不是防 SQL 注入的机制,它不直接防止注入,而是用于在 XML 映射文件中预先定义一个变量(绑定表达式结果)供后续 SQL 使用。它的安全性完全取决于你绑定的内容是否来自用户输入、以及是否用了 #{} 还是 ${}。
换句话说:
✅ <bind></bind> 配合 #{} 可以安全使用;
❌ <bind></bind> 配合 ${} 仍会引入 SQL 注入风险。
一、<bind></bind> 的作用和基本用法
<bind></bind> 标签把 OGNL 表达式的结果绑定为一个局部变量,可在同一条 SQL 中多次引用,常用于简化重复逻辑或预处理参数(比如拼接 like 模糊查询通配符)。
示例(安全写法):
<select id="findUsersByName" resulttype="User"><bind name="pattern" value="'%' + username + '%'"></bind>
SELECT * FROM user WHERE username LIKE #{pattern}
</select>
-
value="'%' + username + '%'"是 OGNL 表达式,运行在 MyBatis 解析阶段(Java 层),不是 SQL 拼接; -
#{pattern}是参数化占位,最终走 PreparedStatement 预编译 → ✅ 安全。
执行时等效于:
SELECT * FROM user WHERE username LIKE ? -- 参数值为 "%张三%"
二、为什么 <bind></bind> 不等于“防注入”,关键看怎么用
| 写法 | 是否安全 | 原因 |
|---|---|---|
<bind name="col" value="columnName"></bind> ORDER BY ${col} |
❌ 危险 |
${col} 直接字符串替换,若 columnName 来自用户(如前端传 id; DROP TABLE user--),就会执行恶意 SQL |
<bind name="col" value="'id'"></bind> ORDER BY ${col} |
✅ 安全(仅限固定值) |
value 是硬编码字符串,无外部输入,但这种写法失去动态意义 |
<bind name="pattern" value="'%' + keyword + '%'"></bind> WHERE name LIKE #{pattern} |
✅ 安全 |
keyword 是方法参数,经 #{} 绑定,全程预编译 |
⚠️ 注意:<bind></bind> 中的 value 表达式如果引用了用户可控参数(如 username、sortField),而后续又用 ${} 引用该变量,就等于绕过 #{} 防护 → 注入照旧发生。
三、常见误用场景与正确替代方案
场景1:模糊查询(like)想加 %,但不敢用 #{} 直接拼
- ❌ 错误尝试(以为 bind 能“兜底”):
<bind name="likeVal" value="'%' + username + '%'"></bind> WHERE username LIKE '${likeVal}' <!-- 用 ${} → 注入! --> - ✅ 正确做法(坚持
#{}):<bind name="pattern" value="'%' + username + '%'"></bind> WHERE username LIKE #{pattern}
场景2:动态排序字段(order by)
- ❌ 危险写法(不可信字段直插):
<bind name="sortBy" value="sortField"></bind> ORDER BY ${sortBy} - ✅ 安全做法(白名单校验 +
#{}不适用,只能${},但必须限制输入):<!-- Java 层先校验 sortField 是否在 ["id", "username", "create_time"] 中 --> <bind name="safeSort" value='@org.apache.commons.lang3.StringUtils@defaultString(sortField, "id")'></bind><!-- 但仍需配合 Java 层白名单判断,不能只靠 bind --> ORDER BY ${safeSort}
? 关键原则:
<bind></bind>是“变量声明工具”,不是“安全过滤器”。防注入的唯一可靠路径,是所有用户输入都走#{};只有极少数受控场景(如排序字段、表名、列名)才允许${},且必须配合 Java 层白名单校验。
四、总结:如何让 <bind></bind> 真正“助力”安全
- 所有含用户输入的绑定值,后续一律用
#{变量名},绝不使用${变量名}; -
<bind></bind>中的 OGNL 表达式避免调用危险方法(如java.lang.Runtime.exec—— 实际会被 MyBatis 默认禁用,但需知其边界); - 复杂场景(如 like + order by + in)优先用 MyBatis 原生动态标签(
<if></if>、<foreach></foreach>、<choose></choose>)配合#{},比手动 bind 更清晰可控; - 若必须动态列名/表名,
<bind></bind>可辅助做前置转换,但校验逻辑必须落在 Java 层(如Enum.valueOf()或Set.contains())。
安全不靠语法糖,靠设计意识。<bind></bind> 很轻巧,用对了省事,用错了就是隐形漏洞。











