MyBatis 防止 SQL 注入的正确用法
一句话总结
MyBatis 防 SQL 注入的核心是:参数一律使用 #{} 做“参数绑定”(PreparedStatement 的 ? 占位),让“SQL 结构”和“数据”彻底分离;对不得不用 ${} 的 表名/列名/排序 等 SQL 片段,必须做 白名单 或用 <choose> 枚举替代;同时配合代码规范、审计与测试实现多层防护。
1. 先搞清楚:SQL 注入的本质是什么?
SQL 注入并不是“没转义”,而是:把用户输入拼进 SQL 语法结构里,导致输入从“数据”变成“指令”。
因此最可靠的防护手段是:
- 参数绑定(PreparedStatement):输入永远作为参数值传递,不参与 SQL 语法解析。
2. MyBatis 两种替换方式:#{}vs${}(核心)
| 写法 | 本质 | 是否安全 | 适用场景 |
|---|---|---|---|
#{param} | 生成 ? 占位符 + JDBC 参数绑定(TypeHandler) | 安全 | WHERE/INSERT/UPDATE 的“值” |
${param} | 文本原样拼接到 SQL(字符串替换) | 高风险 | 表名/列名/ORDER BY 等“SQL 片段” |
结论:
- 能用
#{}就绝不用${}。 ${}只在“无法用?占位”的场景出现,并且必须白名单。
3. 正确用法 1:所有“值”都用#{}(参数绑定)
3.1 基本示例
<select id="findByName" resultType="User">
SELECT * FROM user WHERE name = #{name}
</select>SQL 形态:
SELECT * FROM user WHERE name = ?
此时即使输入是 admin' OR '1'='1,也只会作为一个字符串值去匹配,不会改变 SQL 结构。
3.2 关键点补充:TypeHandler / JDBC 类型处理
#{} 会走 MyBatis 的 TypeHandler,由它决定用 setString/setInt/... 绑定参数。
- 这不是简单“字符串转义”,而是参数绑定。
- 防注入的关键在于“结构与数据分离”。
4. 正确用法 2:动态 SQL 标签是安全的(前提是内部仍用#{})
MyBatis 的动态 SQL(<if> / <where> / <trim> / <choose>)只是动态组装 SQL 结构,并不等于把用户输入拼进 SQL。
只要条件值仍用 #{},就是安全的:
<select id="query" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>5. 常见攻击面与正确写法
5.1LIKE模糊查询
推荐(仍用 #{}):
WHERE name LIKE CONCAT('%', #{name}, '%')不要用:
WHERE name LIKE '%${name}%'注入后果示例:
若用户输入 name 为 ' OR '1'='1,生成的 SQL 将变为:WHERE name LIKE '%' OR '1'='1'%'
由于 '1'='1' 恒成立,该查询会绕过名称过滤,直接返回全表数据,导致敏感信息泄露。
5.2IN查询(批量参数)
使用 <foreach> 生成多个 #{}:
<select id="selectByIds" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>不要把整段 ids 字符串塞进 ${}。
风险/后果:
如果把用户输入的 ids 直接拼成 IN (${ids}),攻击者可构造如 1) OR 1=1 --,最终 SQL 可能变为:WHERE id IN (1) OR 1=1 --)
从而导致 IN 条件被绕过、返回全表数据,甚至在支持多语句的配置下进一步造成更严重的破坏。
5.3 分页参数
分页参数也属于“值”,用 #{}:
SELECT * FROM user LIMIT #{offset}, #{pageSize}6. 不得不用${}的场景:必须白名单/枚举替代
6.1 为什么这些场景不能用#{}?
JDBC 的 ? 占位符只能替换值,不能替换:
- 表名、列名
ORDER BY的字段ASC/DESC关键字
因此这些场景可能出现 ${}。
6.2 推荐做法:用<choose>枚举排序字段(避免透传${sortField})
<select id="findUsers" resultType="User">
SELECT * FROM user
<choose>
<when test="sortField == 'age'">ORDER BY age</when>
<when test="sortField == 'name'">ORDER BY name</when>
<otherwise>ORDER BY id</otherwise>
</choose>
<choose>
<when test="sortOrder == 'DESC'">DESC</when>
<otherwise>ASC</otherwise>
</choose>
</select>6.3 业务侧白名单映射(如果必须用${})
- 输入只允许来自固定集合(枚举/Map 映射),严禁直接使用用户原始字符串。
- 对排序字段、表名、列名做正向校验(white list),不要做“黑名单过滤”。
7. 常见误区(纠错点)
- 误区:
#{}是“自动转义”所以安全- 更准确:
#{}是 参数绑定(结构与数据分离),不是靠转义。
- 更准确:
- 误区:动态 SQL 一定安全
- 动态标签本身安全,但如果你在标签里用
${}拼接用户输入,一样会注入。
- 动态标签本身安全,但如果你在标签里用
- 误区:把
'${name}'包上引号就安全- 仍是拼接,依然可能被构造输入突破。
8. 多层防护(生产建议)
仅靠 ORM 习惯不够,建议组合:
- 编码规范:禁止
${}透传用户输入;代码评审强制检查 - 参数校验:对排序字段、表名等做枚举/白名单校验
- 审计与测试:单测/集成测试加入注入 payload(如
"' OR '1'='1") - 最小权限:DB 账号权限最小化(禁止高危权限)
- 可选:WAF/SQL 防火墙/数据库审计(兜底)
9. 面试速答
- Q:MyBatis 如何防 SQL 注入?
- A:对值使用
#{}走 PreparedStatement 参数绑定;对必须拼接的 SQL 片段(表名/列名/排序)使用白名单或<choose>枚举,禁止${}透传用户输入。
- A:对值使用
- Q:
${}为什么危险?- A:它是文本替换,输入会进入 SQL 结构,可能变成指令。
- Q:
IN/LIKE怎么写才安全? - A:
IN用<foreach>+#{};LIKE用CONCAT('%', #{x}, '%')。
到此这篇关于MyBatis 防止 SQL 注入的正确用法的文章就介绍到这了,更多相关MyBatis 防止 SQL 注入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Java实现Word、Excel、PDF文件格式互转的几种实现方式
在Java中实现文档格式转换通常需要使用专门的库,下面我将介绍如何使用Apache POI、iText和Aspose等库来实现这些转换,有需要的小伙伴可以了解下2025-12-12
java链表数据结构LinkedList插入删除元素时间复杂度面试精讲
这篇文章主要为大家介绍了java LinkedList插入和删除元素的时间复杂度面试精讲,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-10-10
Java8 将一个List<T>转为Map<String,T>的操作
这篇文章主要介绍了Java8 将一个List<T>转为Map<String, T>的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2021-02-02


最新评论