MyBatis防止SQL注入详解
1️⃣ SQL注入是什么?(故事举例)
想象一个银行系统:用户输入账号密码登录。如果开发人员直接拼接SQL:
String sql = "SELECT * FROM users WHERE account='"+account+"' AND pwd='"+pwd+"'";
黑客输入账号:admin' --
(--
是SQL注释符),密码随意。实际执行的SQL变成:
SELECT * FROM users WHERE account='admin' -- ' AND pwd='任意值'
相当于直接登录管理员账号!这就是SQL注入攻击。
2️⃣ MyBatis的防御机制
核心是通过 #{}
参数占位符 实现预编译:
<select id="findUser">
SELECT * FROM users
WHERE account = #{account} <!-- MyBatis自动处理为预编译参数 -->
AND pwd = #{pwd}
</select>
执行过程:
- MyBatis先将SQL模板发送给数据库:
SELECT * FROM users WHERE account=? AND pwd=?
- 数据库预编译该模板(确定语法结构)
- 再将参数值
account='admin'--
安全传递 - 最终执行时,参数始终被当作纯文本值,无法改变SQL结构
3️⃣ vs 危险写法 ${}
<!-- 危险!拼接SQL -->
<select id="findUser">
SELECT * FROM users
WHERE account = '${account}' <!-- 直接文本替换 -->
</select>
输入account=' OR 1=1 --
时,SQL变成:
SELECT * FROM users WHERE account='' OR 1=1 -- '
所有账户泄露!
4️⃣ 特殊场景处理
必须用${}
时(如动态表名/排序字段),需手动过滤:
// 白名单校验
private static final Set<String> SAFE_COLUMNS = Set.of("name","age");
public String safeOrder(String column) {
if(!SAFE_COLUMNS.contains(column))
throw new IllegalArgumentException("非法字段");
return "ORDER BY ${" + column + "}";
}
5️⃣ 额外防护建议
- 📌 始终优先用
#{}
- 📌 启用MyBatis的
log4jdbc
日志,监控实际SQL - 📌 对用户输入做正则校验(如账号只允许字母数字)
- 📌 定期使用SQL注入扫描工具(如SQLMap)
总结
机制 | 安全性 | 原理 | 使用场景 |
---|---|---|---|
#{} | ✅ 安全 | 预编译参数绑定 | 99%场景推荐 |
${} | ❌ 危险 | 字符串拼接 | 动态表/列名需过滤 |
手动过滤 | ⚠️ 可控 | 白名单/类型校验 | 不得已时使用 |
🔑 核心原则:让数据和代码分离。预编译使SQL结构固定不变,用户输入仅作为"数据"传递,无法篡改"代码"结构。