SQL注入是后端开发中最常见的安全漏洞之一,攻击者通过构造恶意参数,拼接到底层SQL语句中,篡改查询逻辑,窃取、篡改甚至删除数据库数据,造成严重的安全隐患。MyBatis作为主流的持久层框架,内置了完善的SQL注入防御机制,核心在于正确使用参数占位符和规范SQL编写。本文将聚焦MyBatis防止SQL注入的核心方法,结合独立构思的代码示例,清晰拆解每种防御手段的原理、实现方式及注意事项,帮你从根源上规避SQL注入风险,轻松应对面试考点。
一、一句话吃透MyBatis防SQL注入核心
MyBatis防止SQL注入的核心是依托JDBC预编译机制,通过#{}占位符实现参数安全绑定,自动对参数进行转义;同时严格限制${}的使用场景并做好参数校验,配合动态SQL标签规范SQL拼接,从参数传递、SQL编写两个层面阻断注入攻击。
二、核心防御手段一:优先使用#{}占位符(最关键)
核心原理
#{}是MyBatis最安全的参数占位符,底层会将其转换为JDBC中PreparedStatement的?占位符,执行预编译操作。预编译过程中,MyBatis会自动对参数值进行类型校验和特殊字符转义,将参数当作纯数据解析,而非SQL代码的一部分,从根本上杜绝SQL注入。
代码示例(安全实现)
以“根据用户名查询用户信息”为场景,展示#{}的正确使用方式,以及注入攻击被阻断的效果:
// 实体类:User(省略getter、setter、toString方法)
public class User {
private Integer userId;
private String username;
private String password;
private String email;
}
// Mapper接口
public interface UserMapper {
// 用#{}接收参数,安全无注入风险
User selectUserByUsername(@Param("username") String username);
}
// Mapper XML映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUserByUsername" parameterType="String" resultType="com.example.entity.User">
SELECT user_id, username, password, email FROM t_user WHERE username = #{username}
</select>
</mapper>
注入攻击阻断效果
假设攻击者构造恶意参数:username = "test' OR '1'='1 --",试图查询所有用户数据,此时MyBatis的处理逻辑如下:
-
预编译阶段:#{}被替换为?,生成的SQL为:SELECT user_id, username, password, email FROM t_user WHERE username = ?;
-
参数填充阶段:恶意参数中的单引号、--等特殊字符被自动转义,最终参数被当作纯字符串处理,执行的SQL等价于:
SELECT user_id, username, password, email FROM t_user WHERE username = 'test'' OR ''1''=''1 --'
此时查询条件变为“用户名等于test' OR '1'='1 --”,数据库中无对应数据,攻击失败,有效阻断SQL注入。
三、核心防御手段二:严格限制${}的使用(规避风险)
${}的风险本质
与#{}不同,${}会直接将参数值以字符串形式拼接到SQL语句中,不做任何转义处理。若参数来自用户输入且未做校验,攻击者可构造恶意参数,篡改SQL逻辑,引发注入攻击。
错误示例(存在注入风险)
<!-- 危险:直接使用${}接收用户输入,无任何校验 -->
<select id="selectUserByUsername" parameterType="String" resultType="com.example.entity.User">
SELECT user_id, username, password, email FROM t_user WHERE username = '${username}'
</select>
当攻击者输入上述恶意参数username = "test' OR '1'='1 --"时,拼接后的SQL会变为:
SELECT user_id, username, password, email FROM t_user WHERE username = 'test' OR '1'='1 --'
其中--是SQL注释符号,会注释掉后续语句,实际执行的逻辑是“查询用户名等于test,或1=1”,而1=1恒成立,最终会查询出表中所有用户数据,造成数据泄露。
${}的安全使用场景与校验方法
{},但必须对参数进行严格校验,确保参数合法性。
安全使用示例(动态表名)
假设系统存在分表(t_user_2024、t_user_2025),需根据年份动态选择表名,此时可使用${},但需通过白名单校验表名合法性:
// Mapper接口
public interface UserMapper {
List<User> selectUserByYear(@Param("tableName") String tableName);
}
// 服务层:对tableName进行白名单校验(核心)
public class UserService {
@Autowired
private UserMapper userMapper;
public List<User> getUserByYear(String year) {
// 白名单:仅允许指定的表名
String tableName = "t_user_" + year;
List<String> validTableNames = Arrays.asList("t_user_2024", "t_user_2025");
if (!validTableNames.contains(tableName)) {
throw new IllegalArgumentException("非法的表名,禁止查询");
}
return userMapper.selectUserByYear(tableName);
}
}
// Mapper XML映射文件
<select id="selectUserByYear" parameterType="String" resultType="com.example.entity.User">
SELECT user_id, username FROM ${tableName}
</select>
通过白名单校验,确保tableName只能是预定义的合法值,即使攻击者构造恶意参数,也会被拦截,避免注入风险。
安全使用示例(动态排序字段)
// 服务层:校验排序字段和排序方向
public List<User> getUserList(String sortField, String sortOrder) {
// 白名单校验:排序字段只能是表中存在的列
List<String> validSortFields = Arrays.asList("user_id", "username", "email");
if (!validSortFields.contains(sortField)) {
throw new IllegalArgumentException("非法的排序字段");
}
// 校验排序方向:只能是ASC或DESC
if (!"ASC".equals(sortOrder) && !"DESC".equals(sortOrder)) {
sortOrder = "ASC"; // 默认升序
}
return userMapper.selectUserList(sortField, sortOrder);
}
// Mapper接口
List<User> selectUserList(@Param("sortField") String sortField, @Param("sortOrder") String sortOrder);
// Mapper XML映射文件
<select id="selectUserList" resultType="com.example.entity.User">
SELECT user_id, username, email FROM t_user ORDER BY ${sortField} ${sortOrder}
</select>
四、核心防御手段三:使用MyBatis动态SQL标签(规范拼接)
核心原理
MyBatis提供了、、、等动态SQL标签,用于安全拼接SQL条件,避免开发者手动拼接SQL字符串,从而减少注入风险。这些标签会自动处理SQL语法(如自动添加AND/OR、去除多余逗号),同时配合#{}使用,进一步提升安全性。
代码示例(动态SQL安全拼接)
以“多条件查询用户”为场景,使用动态SQL标签拼接条件,避免手动拼接带来的注入风险:
<select id="selectUserByCondition" parameterType="map" resultType="com.example.entity.User">
SELECT user_id, username, password, email FROM t_user
<where>
<!-- 动态拼接条件,配合#{}使用,安全无注入 -->
<if test="username != null and username != ''">
AND username = #{username}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="userId != null">
AND user_id = #{userId}
</if>
</where>
</select>
说明:标签会自动去除条件前多余的AND,避免手动拼接时出现“WHERE AND ...”的语法错误;同时所有参数均使用#{},确保参数安全,即使条件来自用户输入,也不会引发注入攻击。
五、核心防御手段四:全局配置与规范开发
1. 全局配置优化
在MyBatis核心配置文件(mybatis-config.xml)中,可通过相关配置进一步提升安全性,同时规范开发:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 开启驼峰命名映射,避免手动编写列名时出错,减少SQL拼接需求 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启日志打印,便于排查SQL问题,及时发现异常拼接 -->
<setting name="logImpl" value="SLF4J"/>
</settings>
</configuration>
2. 规范开发注意事项
除了上述手段,日常开发中还需遵循以下规范,从源头规避注入风险:
-
所有用户输入的参数,均使用#{}接收,杜绝使用${}接收未校验的用户输入;
-
若必须使用${},务必通过白名单校验参数,禁止直接拼接用户输入的原始值;
-
避免手动拼接SQL字符串,即使是简单的条件拼接,也优先使用MyBatis动态SQL标签;
-
敏感字段(如密码)需进行加密存储,即使发生注入攻击,也能减少数据泄露损失;
-
定期进行代码审查,排查是否存在${}滥用、SQL手动拼接等安全隐患。
六、防御手段总结表
| 防御手段 | 核心原理 | 适用场景 | 注意事项 |
|---|---|---|---|
| 优先使用#{} | 预编译机制,自动转义参数,参数与SQL指令分离 | 所有用户输入的参数传递(WHERE条件、INSERT/UPDATE值等) | 无需手动转义,直接使用即可 |
| 限制${}使用+白名单校验 | 仅在必要时使用,通过预定义合法值拦截恶意参数 | 动态表名、动态列名、排序字段等SQL片段拼接 | 必须进行白名单校验,禁止直接使用用户原始输入 |
| 使用动态SQL标签 | 自动处理SQL语法,避免手动拼接字符串 | 多条件查询、动态拼接SQL条件 | 配合#{}使用,避免在标签内使用${}接收用户输入 |
| 全局配置+规范开发 | 优化配置、规范编码,减少安全隐患 | 所有MyBatis开发场景 | 定期代码审查,排查安全漏洞 |
以上就是MyBatis防止SQL注入的核心方法,核心在于“优先使用#{}、严格限制${}、规范SQL编写”。面试时,只需抓住预编译机制和参数校验这两个关键点,就能清晰作答;实际开发中,遵循上述规范,就能从根源上阻断SQL注入攻击,保障系统数据安全。需要注意的是,SQL注入防御是一个系统性的工作,除了依赖MyBatis的内置机制,还需结合后端代码校验、数据库权限控制等手段,形成完整的安全防御体系。