Tavio's blog Tavio's blog
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Tavio Zhang

努力学习的小码喽
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MyBatis Mapper动态代理
  • MyBatis缓存机制
  • MyBatis SQL注入防护
    • 一、SQL注入基础:原理与危害
      • 1.1 什么是SQL注入?
      • 1.2 SQL注入的典型危害
      • 1.3 典型SQL注入案例(原生JDBC对比)
    • 二、MyBatis中SQL注入的风险源头:#{} vs ${}
      • 2.1 #{}与${}的本质区别
      • 2.2 风险验证:${}导致SQL注入的实战案例
      • 场景1:用户登录(直接拼接用户名)
      • 场景2:动态排序(拼接排序字段)
      • 2.3 关键结论
    • 三、MyBatis核心防护机制:预编译与参数绑定
      • 3.1 #{}的解析与预编译流程
      • 3.2 #{}的参数转义机制
      • 3.3 底层源码佐证:#{}的预编译处理
    • 四、MyBatis SQL注入防护实战:安全写法与风险规避
      • 4.1 基础场景:查询/新增/更新参数
      • 4.2 特殊场景1:动态表名/列名(必须用${}时)
      • 4.3 特殊场景2:动态排序(ORDER BY)
      • 4.4 特殊场景3:LIKE查询(避免错误使用${})
      • 4.5 特殊场景4:IN查询(避免${}拼接多个参数)
    • 五、全链路防护:不止于编码的安全保障
      • 5.1 输入参数校验:前端+后端双重拦截
      • 5.2 MyBatis配置加固
      • 5.3 数据库权限最小化
      • 5.4 依赖包升级:避免框架漏洞
      • 5.5 安全审计与监控
    • 六、常见问题与避坑指南
      • 6.1 问题1:“用了#{}就一定安全吗?”
      • 6.2 问题2:“MyBatis的注解方式(@Select)是否更安全?”
      • 6.3 问题3:“存储过程中的参数会导致注入吗?”
      • 6.4 问题4:“批量插入/更新时用${}是否安全?”
    • 七、总结:MyBatis SQL注入防护核心原则
  • 《MyBatis》笔记
Tavio
2024-03-19
目录

MyBatis SQL注入防护

SQL注入是Web应用中最常见且危害极大的安全漏洞之一。攻击者通过构造恶意SQL语句注入到应用的输入参数中,欺骗数据库执行非预期操作,可能导致数据泄露、篡改、删除甚至服务器权限被窃取。MyBatis作为主流ORM框架,虽然通过自身设计降低了SQL注入风险,但不规范的使用仍会让应用暴露在攻击之下。

# 一、SQL注入基础:原理与危害

在深入MyBatis的防护机制前,我们先明确SQL注入的核心逻辑——本质是“恶意参数破坏SQL语句结构,导致数据库执行非预期逻辑”。

# 1.1 什么是SQL注入?

SQL注入是一种代码注入攻击,攻击者利用应用程序对输入参数的校验不足,将恶意SQL片段注入到SQL语句中,使拼接后的SQL语句被数据库当作合法指令执行。其核心前提是:应用程序将用户输入直接拼接到SQL语句中,未做任何过滤或转义。

# 1.2 SQL注入的典型危害

  • 数据泄露:查询敏感数据(如用户密码、银行卡信息),例如通过注入获取全量用户数据;
  • 数据篡改/删除:修改或删除核心业务数据(如订单、账户余额),甚至执行DROP TABLE等破坏性操作;
  • 权限提升:通过注入获取数据库管理员权限,进一步控制服务器;
  • 服务器入侵:结合数据库特性(如MySQL的LOAD_FILE、INTO OUTFILE)读取/写入服务器文件,植入恶意代码。

# 1.3 典型SQL注入案例(原生JDBC对比)

在原生JDBC中,直接拼接用户输入的SQL语句是导致注入的直接原因。例如用户登录场景:

// 危险写法:直接拼接用户输入
String username = request.getParameter("username");
String password = request.getParameter("password");
String sql = "SELECT * FROM user WHERE username = '" + username + "' AND password = '" + password + "'";
// 当攻击者输入 username = "admin' OR '1'='1" 时,拼接后的SQL为:
// SELECT * FROM user WHERE username = 'admin' OR '1'='1' AND password = 'xxx'
// 由于 '1'='1' 恒成立,无需正确密码即可登录成功
1
2
3
4
5
6
7

而安全的写法是使用PreparedStatement预编译SQL,将参数与SQL语句分离:

// 安全写法:预编译SQL,参数占位符
String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username); // 参数单独设置,自动转义
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
1
2
3
4
5
6

预编译的核心优势是:SQL语句的结构在编译阶段就已确定,用户输入仅作为参数填充到占位符中,无法改变SQL的原有逻辑,从而从根源上避免注入。而MyBatis的核心防护机制,正是基于预编译实现的。

# 二、MyBatis中SQL注入的风险源头:#{} vs ${}

MyBatis提供了两种参数占位符:#{}和${}。两者的核心区别在于“是否触发预编译”,这也是MyBatis中SQL注入风险的核心源头。

# 2.1 #{}与${}的本质区别

我们从“解析方式”“参数处理”“安全程度”三个维度对比两者:

对比维度 #{}(预编译占位符) ${}(字符串拼接)
解析方式 MyBatis将其解析为PreparedStatement的?占位符,触发JDBC预编译 MyBatis直接将参数值作为字符串拼接到SQL语句中,不触发预编译
参数处理 自动对参数进行类型转换和特殊字符转义(如单引号'转义为'') 直接拼接原始参数值,无任何转义处理
安全程度 安全,从根源避免SQL注入 危险,存在SQL注入风险(除非参数已做严格校验)
适用场景 绝大多数场景:查询条件参数、插入/更新的字段值等 特殊场景:表名、列名动态拼接、排序字段(ORDER BY)、分组字段(GROUP BY)等

# 2.2 风险验证:${}导致SQL注入的实战案例

我们以“用户登录”和“动态排序查询”两个常见场景为例,验证${}的注入风险。

# 场景1:用户登录(直接拼接用户名)

Mapper.xml危险写法(使用${}拼接用户名):

<!-- UserMapper.xml:危险写法 -->
<select id="login" resultType="com.example.entity.User">
  SELECT id, username, password FROM user 
  WHERE username = '${username}' AND password = '${password}'
</select>
1
2
3
4
5

攻击演示:

当攻击者输入:

  • username = "admin' OR '1'='1"
  • password = "任意值"

拼接后的SQL语句:

SELECT id, username, password FROM user 
WHERE username = 'admin' OR '1'='1' AND password = '任意值'
1
2

由于'1'='1'恒成立,OR条件使整个查询条件成立,攻击者无需正确密码即可登录成功,甚至可以通过注入"admin' OR 1=1 UNION SELECT * FROM user--"获取全量用户数据。

# 场景2:动态排序(拼接排序字段)

很多业务需要支持“动态排序”(如按时间、按金额排序),若直接用${}拼接排序字段,会存在注入风险:

<!-- UserMapper.xml:危险写法 -->
<select id="listUser" resultType="com.example.entity.User">
  SELECT id, username, create_time FROM user 
  ORDER BY ${sortField} ${sortOrder}
</select>
1
2
3
4
5

攻击演示:

当攻击者输入:

  • sortField = "create_time; DROP TABLE user--"
  • sortOrder = "ASC"

拼接后的SQL语句:

SELECT id, username, create_time FROM user 
ORDER BY create_time; DROP TABLE user-- ASC
1
2

其中;用于分隔SQL语句,--用于注释后续内容,导致数据库先执行查询,再执行DROP TABLE user,直接删除核心表,造成毁灭性损失。

# 2.3 关键结论

MyBatis中SQL注入的核心风险源是${}的不规范使用——由于其直接拼接字符串的特性,攻击者可通过构造恶意参数改变SQL语句结构。而#{}通过预编译机制从根源避免了注入风险,是MyBatis的默认安全方案。

# 三、MyBatis核心防护机制:预编译与参数绑定

MyBatis的核心防护能力来源于对JDBCPreparedStatement预编译机制的封装。我们从“解析流程”“参数处理”“底层原理”三个层面拆解其防护逻辑。

# 3.1 #{}的解析与预编译流程

当MyBatis解析包含#{}的Mapper语句时,会执行以下流程:

  1. SQL语句预处理:MyBatis将#{paramName}替换为?,生成预编译SQL语句(如SELECT * FROM user WHERE username = ?);
  2. 预编译请求:MyBatis通过JDBC将预编译SQL发送给数据库,数据库对SQL语句进行编译(确定执行计划),并缓存编译结果;
  3. 参数绑定与执行:MyBatis将用户输入的参数值通过PreparedStatement.setXxx()方法绑定到?占位符上,数据库直接使用缓存的执行计划执行SQL,参数仅作为数据填充,不参与SQL语句的解析。

核心优势:SQL语句的结构在编译阶段就已固定,用户输入无论是什么内容,都只能作为参数填充,无法改变SQL的原有逻辑,从而彻底杜绝注入。

# 3.2 #{}的参数转义机制

除了预编译,MyBatis还会对#{}传入的参数进行自动转义,进一步保障安全。例如,当参数中包含单引号'时,MyBatis会自动将其转义为''(数据库的转义规则),避免参数中的单引号破坏SQL语句的引号配对。

示例:若用户输入username = "admin'",使用#{username}的SQL会被处理为:

SELECT * FROM user WHERE username = 'admin'''
1

此时数据库会将''解析为单个单引号(字符串内的转义),查询条件变为“用户名等于admin'”,而非破坏SQL结构。

# 3.3 底层源码佐证:#{}的预编译处理

MyBatis在解析#{}时,会通过ParameterMapping封装参数信息,并生成预编译SQL。核心源码片段来自XmlStatementBuilder和BoundSql:

// MyBatis解析#{}的核心逻辑(简化)
public class XmlStatementBuilder {
  private void processSelectKeyNodes(String id, SelectKeyGenerator keyGenerator, XmlNode nodeToHandle, String parameterTypeClass, LanguageDriver langDriver) {
    // 解析Mapper中的SQL语句,将#{xxx}替换为?
    BoundSql boundSql = langDriver.createBoundSql(configuration, sql, parameterMappings, parameterObject);
    // 生成PreparedStatement所需的预编译SQL
    String sql = boundSql.getSql(); // 此时sql中的#{}已被替换为?
  }
}

// BoundSql类:封装预编译后的SQL和参数映射
public class BoundSql {
  private final String sql; // 预编译后的SQL(含?占位符)
  private final List<ParameterMapping> parameterMappings; // 参数映射信息
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

从源码可见,MyBatis在解析阶段就已将#{}转换为预编译占位符,后续通过PreparedStatement执行,确保参数与SQL分离。

# 四、MyBatis SQL注入防护实战:安全写法与风险规避

基于以上原理,我们针对MyBatis的常见使用场景,给出“危险写法”与“安全写法”的对比,并重点说明${}的安全使用方式(无法避免使用时)。

# 4.1 基础场景:查询/新增/更新参数

此类场景是最常见的参数传递场景,必须使用#{},严禁使用${}。

<!-- 场景:根据ID查询用户 -->
<!-- 危险写法 -->
<select id="getUserById" resultType="com.example.entity.User">
  SELECT * FROM user WHERE id = ${id} <!-- 风险:直接拼接ID,若ID为字符串类型可注入 -->
</select>

<!-- 安全写法 -->
<select id="getUserById" resultType="com.example.entity.User">
  SELECT * FROM user WHERE id = #{id} <!-- 预编译,自动转义,安全 -->
</select>
1
2
3
4
5
6
7
8
9
10
<!-- 场景:新增用户 -->
<!-- 危险写法 -->
<insert id="addUser" parameterType="com.example.entity.User">
  INSERT INTO user(username, password) VALUES('${username}', '${password}')
</insert>

<!-- 安全写法 -->
<insert id="addUser" parameterType="com.example.entity.User">
  INSERT INTO user(username, password) VALUES(#{username}, #{password})
</insert>
1
2
3
4
5
6
7
8
9
10

# 4.2 特殊场景1:动态表名/列名(必须用${}时)

当需要动态指定表名(如分表场景:user_202401、user_202402)或列名时,无法使用#{}}(预编译的表名/列名会被当作字符串处理,导致SQL语法错误),此时必须使用${},但需通过“白名单校验”规避风险。

示例:分表查询用户数据

<!-- UserMapper.xml:使用${}拼接表名,但需在Java代码中做白名单校验 -->
<select id="listUserByTable" resultType="com.example.entity.User">
  SELECT * FROM user_${month} <!-- month为分表后缀,如202401 -->
  WHERE status = #{status}
</select>
1
2
3
4
5

安全防护:在Java代码中对month参数做白名单校验,确保其只能是合法的分表后缀:

public List<User> listUserByTable(String month, Integer status) {
  // 白名单校验:仅允许202401-202412的分表后缀
  List<String> validMonths = Arrays.asList("202401", "202402", ..., "202412");
  if (!validMonths.contains(month)) {
    throw new IllegalArgumentException("非法的分表参数");
  }
  // 校验通过后调用Mapper
  return userMapper.listUserByTable(month, status);
}
1
2
3
4
5
6
7
8
9

核心原则:动态表名/列名的参数必须是有限可选值,通过白名单校验确保参数合法,禁止直接接收用户输入的原始值。

# 4.3 特殊场景2:动态排序(ORDER BY)

排序字段(sortField)和排序方向(sortOrder)无法使用#{}}(会导致SQL语法错误),必须使用${},防护方案同样是“白名单校验”。

<!-- UserMapper.xml:动态排序 -->
<select id="listUser" resultType="com.example.entity.User">
  SELECT id, username, create_time, amount FROM user 
  ORDER BY ${sortField} ${sortOrder}
</select>
1
2
3
4
5

安全防护:在Java代码中对sortField和sortOrder做白名单校验:

public List<User> listUser(String sortField, String sortOrder) {
  // 1. 排序字段白名单:仅允许create_time、amount
  List<String> validSortFields = Arrays.asList("create_time", "amount");
  if (!validSortFields.contains(sortField)) {
    sortField = "create_time"; // 默认值,避免注入
  }
  // 2. 排序方向白名单:仅允许ASC、DESC
  List<String> validSortOrders = Arrays.asList("ASC", "DESC");
  if (!validSortOrders.contains(sortOrder)) {
    sortOrder = "ASC"; // 默认值
  }
  // 调用Mapper
  return userMapper.listUser(sortField, sortOrder);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

补充:若排序字段较多,可通过正则表达式校验(如仅允许字母、下划线组成),进一步缩小参数范围:

if (!sortField.matches("^[a-zA-Z_]+$")) {
  throw new IllegalArgumentException("非法的排序字段");
}
1
2
3

# 4.4 特殊场景3:LIKE查询(避免错误使用${})

LIKE查询是高频场景,很多开发者会错误地使用${}拼接模糊查询条件,导致注入风险。正确的做法是使用#{}}配合字符串拼接(在SQL中使用CONCAT函数)。

<!-- 场景:根据用户名模糊查询 -->
<!-- 危险写法 -->
<select id="searchUserByName" resultType="com.example.entity.User">
  SELECT * FROM user WHERE username LIKE '%${username}%' <!-- 风险:直接拼接 -->
</select>

<!-- 安全写法1:SQL中使用CONCAT函数 -->
<select id="searchUserByName" resultType="com.example.entity.User">
  SELECT * FROM user WHERE username LIKE CONCAT('%', #{username}, '%')
</select>

<!-- 安全写法2:Java代码中拼接%,Mapper中使用#{}}
<select id="searchUserByName" resultType="com.example.entity.User">
  SELECT * FROM user WHERE username LIKE #{username}
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

对应的Java代码:

public List<User> searchUserByName(String username) {
  // Java代码中拼接%,参数仍通过#{}}传递
  String searchParam = "%" + username + "%";
  return userMapper.searchUserByName(searchParam);
}
1
2
3
4
5

# 4.5 特殊场景4:IN查询(避免${}拼接多个参数)

IN查询需要传递多个参数(如IN (1,2,3)),若直接用${}拼接参数列表,会存在注入风险。正确的做法是使用MyBatis的<foreach>标签配合#{}}。

<!-- 场景:根据多个ID查询用户 -->
<!-- 危险写法 -->
<select id="listUserByIds" resultType="com.example.entity.User">
  SELECT * FROM user WHERE id IN (${ids}) <!-- 风险:ids若为"1,2; DROP TABLE user"会注入 -->
</select>

<!-- 安全写法:使用<foreach>标签 -->
<select id="listUserByIds" resultType="com.example.entity.User">
  SELECT * FROM user WHERE id IN 
  <foreach collection="ids" item="id" open="(" close=")" separator=",">
    #{id}
  </foreach>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13

对应的Java代码(参数为Lis<tInteger> ids):

public List<User> listUserByIds(List<Integer> ids) {
  return userMapper.listUserByIds(ids);
}
1
2
3

MyBatis会自动将List中的元素通过#{}}解析为预编译参数,生成IN (?, ?, ?)的安全SQL。

# 五、全链路防护:不止于编码的安全保障

SQL注入防护是全链路的工作,除了规范使用#{}和${},还需要从“输入校验”“配置加固”“依赖升级”“日志监控”等多个维度构建防护体系。

# 5.1 输入参数校验:前端+后端双重拦截

攻击者的恶意参数首先要经过前端和后端的校验拦截,减少恶意参数进入Mapper层的可能性:

  • 前端校验:限制输入长度、过滤特殊字符(如'、;、--、UNION等),给出友好的输入提示;
  • 后端校验:使用校验框架(如Hibernate Validator)对参数进行类型、格式、范围校验,例如:
public class UserQuery {
  @NotNull(message = "用户名不能为空")
  @Size(max = 20, message = "用户名长度不能超过20")
  @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名仅允许字母、数字、下划线")
  private String username;
  
  // 其他参数...
}
1
2
3
4
5
6
7
8

# 5.2 MyBatis配置加固

通过MyBatis的核心配置,进一步提升安全性:

  • 开启参数类型校验:在MyBatis配置文件中开启mapUnderscoreToCamelCase(驼峰命名转换)的同时,确保参数类型与数据库字段类型匹配,避免类型转换导致的注入风险;
  • 禁用unsafe的配置:避免使用defaultStatementType="STATEMENT"(默认是PREPARED),STATEMENT会禁用预编译,强制MyBatis使用字符串拼接;
  • 开启SQL日志监控:配置logImpl=SLF4J,打印执行的SQL语句和参数,便于及时发现异常的SQL注入行为。
<!-- mybatis-config.xml 配置加固 -->
<configuration>
  <settings>
    <!-- 开启驼峰命名转换 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <!-- 禁用STATEMENT,强制使用PREPARED预编译 -->
    <setting name="defaultStatementType" value="PREPARED"/>
    <!-- 开启SQL日志,便于监控 -->
    <setting name="logImpl" value="SLF4J"/>
  </settings>
</configuration>
1
2
3
4
5
6
7
8
9
10
11

# 5.3 数据库权限最小化

即使发生SQL注入,最小化的数据库权限也能降低攻击危害:

  • 应用程序连接数据库的账号,仅授予“必要权限”(如SELECT、INSERT、UPDATE、DELETE),严禁授予DROP、ALTER、CREATE等破坏性权限;
  • 不同业务模块使用不同的数据库账号,避免一个账号泄露导致全库数据风险;
  • 禁止使用数据库管理员(root、sa等)账号连接应用。

# 5.4 依赖包升级:避免框架漏洞

及时升级MyBatis及相关依赖包,修复已知的安全漏洞:

  • MyBatis核心包:升级到最新稳定版本(如3.5.16+),避免旧版本中的解析漏洞;
  • JDBC驱动包:升级到对应数据库的最新驱动版本,修复驱动层的预编译漏洞;
  • 使用依赖管理工具(Maven、Gradle)定期检查依赖漏洞(如通过Dependabot、OWASP Dependency Check)。

# 5.5 安全审计与监控

通过日志监控和安全审计,及时发现并阻断SQL注入攻击:

  • 记录所有执行的SQL语句和参数,重点监控包含特殊字符('、;、UNION、DROP等)的SQL;
  • 使用WAF(Web应用防火墙)拦截常见的SQL注入攻击特征(如OWASP Top 10中的注入攻击特征);
  • 定期进行安全渗透测试,模拟攻击者行为,排查潜在的注入漏洞。

# 六、常见问题与避坑指南

在实际开发中,很多SQL注入风险源于开发者的“疏忽”或“误解”。以下是常见问题及解决方案:

# 6.1 问题1:“用了#{}就一定安全吗?”

不一定。若在同一个SQL语句中混用#{}和${},仍可能存在风险。例如:

<!-- 危险:混用#{}和${} -->
<select id="unsafeQuery" resultType="com.example.entity.User">
  SELECT ${column} FROM user WHERE id = #{id}
</select>
1
2
3
4

解决方案:严格控制${}的使用场景,对所有通过${}传递的参数做白名单校验。

# 6.2 问题2:“MyBatis的注解方式(@Select)是否更安全?”

注解方式与XML方式的安全机制完全一致,核心仍取决于使用#{}还是${}。例如:

// 危险写法(注解中使用${})
@Select("SELECT * FROM user WHERE username = '${username}'")
User login(@Param("username") String username, @Param("password") String password);

// 安全写法(注解中使用#{})
@Select("SELECT * FROM user WHERE username = #{username} AND password = #{password}")
User login(@Param("username") String username, @Param("password") String password);
1
2
3
4
5
6
7

# 6.3 问题3:“存储过程中的参数会导致注入吗?”

会。若存储过程中使用动态SQL拼接,即使MyBatis中使用#{}传递参数,仍可能存在注入风险。例如存储过程中:

-- 危险的存储过程:内部拼接SQL
CREATE PROCEDURE searchUser(IN username VARCHAR(20))
BEGIN
  SET @sql = CONCAT('SELECT * FROM user WHERE username = ''', username, '''');
  PREPARE stmt FROM @sql;
  EXECUTE stmt;
END;
1
2
3
4
5
6
7

解决方案:存储过程内部也应使用预编译参数,避免字符串拼接;或直接在MyBatis中实现业务逻辑,避免复杂存储过程。

# 6.4 问题4:“批量插入/更新时用${}是否安全?”

不安全。批量操作若使用${}拼接参数,风险与单条操作一致。正确的做法是使用MyBatis的<foreach>标签配合#{}:

<!-- 批量插入安全写法 -->
<insert id="batchAddUser" parameterType="java.util.List">
  INSERT INTO user(username, password)
  VALUES
  <foreach collection="list" item="user" separator=",">
    (#{user.username}, #{user.password})
  </foreach>
</insert>
1
2
3
4
5
6
7
8

# 七、总结:MyBatis SQL注入防护核心原则

MyBatis的SQL注入防护,核心是“切断恶意参数对SQL语句结构的破坏”,关键在于理解并规范使用#{}和${}。总结为以下3个核心原则:

  1. 优先使用#{},杜绝滥用${}:除了动态表名、列名、排序字段等特殊场景,所有参数传递都必须使用#{},利用预编译机制从根源避免注入;
  2. ${}必须配合白名单校验:若无法避免使用${},必须对参数进行严格的白名单校验(或正则校验),确保参数是预期的合法值,禁止直接接收用户原始输入;
  3. 构建全链路防护体系:防护不止于编码,还需覆盖输入校验、配置加固、数据库权限控制、依赖升级、日志监控等全链路环节,形成“多层防御”。
编辑 (opens new window)
#MyBatis SQL注入防护
上次更新: 2026/01/21, 19:29:14
MyBatis缓存机制

← MyBatis缓存机制

最近更新
01
订单超时取消
01-21
02
双 Token 登录
01-21
03
长短链接跳转
01-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式