🔐 前言
想象一下:你开了一家银行,客户可以凭姓名和密码查询余额。有一天,黑客在姓名框输入 ' OR '1'='1' --
,密码随意填。若系统将输入直接拼接到 SQL 中,查询将变成:
sql
SELECT balance FROM accounts
WHERE username = '' OR '1'='1' -- ' AND password = '随意密码'
由于 OR '1'='1'
恒为真,且 --
注释了后续条件,攻击者就可绕过验证,获取所有账户信息!
这就是常见的严重漏洞 ------ SQL 注入攻击。
❗ 为什么传统 SQL 拼接如此危险?
以 MyBatis 为例,以下是一个错误示范:
xml
<!-- 直接拼接用户输入,存在高风险 -->
<select id="findUser" resultType="User">
SELECT * FROM users
WHERE username = '${username}' AND password = '${password}'
</select>
如果用户输入:
text
username = ' OR 1=1 --
执行的 SQL 实际变为:
sql
SELECT * FROM users
WHERE username = '' OR 1=1 -- ' AND password = 'anything'
攻击结果:绕过密码验证,暴露所有用户数据!
🛡️ MyBatis 的救星:预编译 SQL(#{}
)
✅ 正确写法
xml
<!-- 使用 #{},预编译避免注入 -->
<select id="findUser" resultType="User">
SELECT * FROM users
WHERE username = #{username} AND password = #{password}
</select>
⚙️ 预编译工作原理详解
-
发送模板:
sqlSELECT * FROM users WHERE username = ? AND password = ?
-
数据库编译模板: 解析并生成执行计划(SQL 结构已固定)
-
绑定参数: 用户输入的
username
与password
作为数据绑定,不参与 SQL 语法结构 -
执行查询: 使用编译好的计划,安全执行
🔍 数据与指令的隔离示例
攻击者输入:' OR 1=1 --
,执行效果如下:
sql
SELECT * FROM users WHERE username = '\' OR 1=1 -- '
✅ 攻击失效! 数据库将输入视为普通字符串,而不是 SQL 指令。
🧾 ${}
与 #{}
的终极对比手册
特性 | ${} (文本替换) |
#{} (预编译参数) |
---|---|---|
工作原理 | 拼接字符串进入 SQL | 占位符 ? ,参数单独传入 |
安全性 | ❌ 极易被注入攻击 | ✅ 安全,防注入 |
特殊字符处理 | 不转义,容易破坏语法结构 | 自动转义,防止构造注入 |
数据类型处理 | 默认视为字符串 | 自动识别类型(数字、日期等) |
执行计划复用 | ❌ 每次拼接结果不同,无法复用 | ✅ 可复用执行计划,性能更优 |
调试日志可读性 | ✅ SQL 显示完整字符串 | ⚠️ 仅显示 ? 占位符 |
🧰 实战建议:安全与灵活的平衡
✅ 绝对安全区
- 所有 用户输入 (如查询条件、登录信息)必须使用
#{}
,禁止拼接
⚠️ 风险可控区
当使用 ${}
拼接动态字段(如表名、列名)时,需确保:
- 参数来源非用户直接输入
- 严格的白名单校验
- 禁止包含
;
、--
、\
等危险字符
🚀 性能优化场景
分页、批量查询(如 IN
查询)可结合 <foreach>
标签实现,确保使用 #{}
包裹每个参数
xml
<!-- IN 查询示例 -->
<select id="findByIds" resultType="User">
SELECT * FROM users WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
✅ 总结:牢记 SQL 安全三原则
🚨 务必牢记:涉及用户输入的 SQL 查询,永远不要使用 ${}
拼接语句!
- 使用
#{}
保证数据与指令分离,防止 SQL 注入 - 使用
${}
时必须建立在白名单 与参数可信的基础上 - 对日志、调试、性能有要求时,合理配合日志插件、安全策略使用
数据库安全无小事,一次 SQL 注入可能导致严重数据泄露。理解 MyBatis 的参数机制,是构建安全系统的基础。永远不要相信任何用户输入! 🧱