MyBatis面试高频:SQL注入防御全解析,新手也能吃透

6 阅读3分钟

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的处理逻辑如下:

  1. 预编译阶段:#{}被替换为?,生成的SQL为:SELECT user_id, username, password, email FROM t_user WHERE username = ?;

  2. 参数填充阶段:恶意参数中的单引号、--等特殊字符被自动转义,最终参数被当作纯字符串处理,执行的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恒成立,最终会查询出表中所有用户数据,造成数据泄露。

${}的安全使用场景与校验方法

并非完全不能使用,在需要动态拼接SQL片段(如动态表名、动态列名、排序字段)时,可使用{}并非完全不能使用,在需要动态拼接SQL片段(如动态表名、动态列名、排序字段)时,可使用{},但必须对参数进行严格校验,确保参数合法性。

安全使用示例(动态表名)

假设系统存在分表(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}
&lt;/select&gt;

通过白名单校验,确保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>
    &lt;settings&gt;
        <!-- 开启驼峰命名映射,避免手动编写列名时出错,减少SQL拼接需求 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/&gt;
        <!-- 开启日志打印便于排查SQL问题及时发现异常拼接 -->
        <setting name="logImpl" value="SLF4J"/>
    </settings>
</configuration>

2. 规范开发注意事项

除了上述手段,日常开发中还需遵循以下规范,从源头规避注入风险:

  1. 所有用户输入的参数,均使用#{}接收,杜绝使用${}接收未校验的用户输入;

  2. 若必须使用${},务必通过白名单校验参数,禁止直接拼接用户输入的原始值;

  3. 避免手动拼接SQL字符串,即使是简单的条件拼接,也优先使用MyBatis动态SQL标签;

  4. 敏感字段(如密码)需进行加密存储,即使发生注入攻击,也能减少数据泄露损失;

  5. 定期进行代码审查,排查是否存在${}滥用、SQL手动拼接等安全隐患。

六、防御手段总结表

防御手段核心原理适用场景注意事项
优先使用#{}预编译机制,自动转义参数,参数与SQL指令分离所有用户输入的参数传递(WHERE条件、INSERT/UPDATE值等)无需手动转义,直接使用即可
限制${}使用+白名单校验仅在必要时使用,通过预定义合法值拦截恶意参数动态表名、动态列名、排序字段等SQL片段拼接必须进行白名单校验,禁止直接使用用户原始输入
使用动态SQL标签自动处理SQL语法,避免手动拼接字符串多条件查询、动态拼接SQL条件配合#{}使用,避免在标签内使用${}接收用户输入
全局配置+规范开发优化配置、规范编码,减少安全隐患所有MyBatis开发场景定期代码审查,排查安全漏洞

以上就是MyBatis防止SQL注入的核心方法,核心在于“优先使用#{}、严格限制${}、规范SQL编写”。面试时,只需抓住预编译机制和参数校验这两个关键点,就能清晰作答;实际开发中,遵循上述规范,就能从根源上阻断SQL注入攻击,保障系统数据安全。需要注意的是,SQL注入防御是一个系统性的工作,除了依赖MyBatis的内置机制,还需结合后端代码校验、数据库权限控制等手段,形成完整的安全防御体系。