一、核心概念
什么是 BlockAttackInnerInterceptor
?
BlockAttackInnerInterceptor
是 MyBatis-Plus 内置的一个SQL 执行分析拦截器,用于在执行 SQL 前进行语义分析,主动阻断可能造成全表更新或全表删除的危险操作。
核心功能
- ✅ 防止
UPDATE
无WHERE
条件 → 避免全表更新 - ✅ 防止
DELETE
无WHERE
条件 → 避免全表删除 - ✅ 防止
UPDATE
/DELETE
仅含WHERE 1=1
或恒真条件 → 防止恶意或错误 SQL - ❌ 不检查
SELECT
(如SELECT * FROM table
不会阻断)
工作原理
- 在 SQL 执行前,MyBatis-Plus 通过
MybatisPlusInterceptor
拦截UPDATE
和DELETE
语句。 - 解析 SQL 的
WHERE
条件部分。 - 判断是否满足“无有效
WHERE
条件”:- 无
WHERE
子句 WHERE
子句为1=1
、true
、id IS NOT NULL
等恒真表达式
- 无
- 若判断为“全表操作”,则抛出异常,阻断执行。
🔍 底层技术:基于 JSQLParser 或 MyBatis-Plus 自研 SQL 解析器进行 AST(抽象语法树)分析。
二、操作步骤(超详细)
第一步:添加依赖(确保版本 ≥ 3.4.0)
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
</dependency>
⚠️
BlockAttackInnerInterceptor
从 MyBatis-Plus 3.4.0 开始引入,旧版本使用已废弃的SqlExplainInterceptor
。
第二步:配置 MybatisPlusInterceptor
并注册插件
创建配置类 MyBatisPlusConfig.java
:
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis-Plus 配置类
*/
@Configuration
public class MyBatisPlusConfig {
/**
* 配置 MyBatis-Plus 拦截器链
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加 SQL 阻断攻击插件(必须放在前面)
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
// 可同时添加其他插件(顺序重要)
// interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁
// interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页
return interceptor;
}
}
✅ 关键点:
- 必须通过
MybatisPlusInterceptor
注册BlockAttackInnerInterceptor
应尽早添加(建议第一个)
第三步:编写测试代码验证阻断功能
场景 1:全表删除(应被阻断)
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper;
/**
* 测试全表删除 → 应被 BlockAttackInnerInterceptor 阻断
*/
public void deleteAll() {
// ❌ 危险操作!无 WHERE 条件
userMapper.delete(null); // 或 new QueryWrapper<>()
}
}
执行结果:
org.mybatis.spring.MyBatisSystemException:
nested exception is com.baomidou.mybatisplus.core.exceptions.MybatisPlusException:
prohibition of full table deletion
场景 2:全表更新(应被阻断)
public void updateAll() {
UserDO user = new UserDO();
user.setAge(99);
// ❌ 危险操作!无 WHERE 条件
userMapper.update(user, null); // 或 new UpdateWrapper<>()
}
执行结果:
MybatisPlusException: prohibition of full table operation
场景 3:正常删除(应通过)
public void deleteUserById(Long id) {
// ✅ 正常操作,有明确 WHERE 条件
userMapper.deleteById(id);
}
执行结果:成功执行,无异常。
场景 4:带条件的更新(应通过)
public void updateUserByName(String name, Integer newAge) {
UserDO user = new UserDO();
user.setAge(newAge);
userMapper.update(user, new UpdateWrapper<UserDO>().eq("name", name));
}
执行结果:成功执行。
第四步:测试 WHERE 1=1
等恒真条件
public void updateWithTrueCondition() {
UserDO user = new UserDO();
user.setAge(100);
// ❌ 虽有 WHERE,但为恒真条件,仍被视为全表操作
userMapper.update(user, new UpdateWrapper<UserDO>().apply("1=1"));
}
执行结果:被阻断,抛出异常。
三、常见错误与解决方案
错误现象 | 原因 | 解决方案 |
---|---|---|
插件不生效 | 未正确注册 BlockAttackInnerInterceptor |
检查配置类是否加载,插件是否添加 |
DELETE 被阻断但 UPDATE 不是 |
插件顺序问题 | 确保 BlockAttackInnerInterceptor 在链中 |
updateById 被阻断 |
传入的 id 为 null |
检查 id 是否为空 |
启动报错 ClassNotFoundException |
版本过低 | 升级到 MP 3.4.0+ |
QueryWrapper 使用不当导致误杀 |
如 .apply("status = status") |
避免使用恒真表达式 |
批量操作被阻断 | updateBatchById 内部可能触发 |
检查批量更新的 id 是否为空 |
四、注意事项
- ✅ 仅拦截
UPDATE
和DELETE
:对INSERT
和SELECT
无影响。 - ✅ 不支持
QueryWrapper.or()
复杂逻辑:过于复杂的WHERE
可能误判,建议避免。 - ✅ 与逻辑删除共存:如果使用
@TableLogic
,DELETE
操作会转为UPDATE
,仍受阻断规则影响。 - ✅ 测试环境可关闭:开发/测试环境可选择性关闭,生产环境必须开启。
- ✅ 异常类型:抛出
MybatisPlusException
,建议全局异常处理器捕获并返回友好提示。 - ✅ 性能影响极小:SQL 解析开销可忽略,适用于高并发场景。
五、使用技巧
1. 开发环境动态关闭插件
@Bean
@Profile({"prod"}) // 仅在生产环境启用
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
if ("prod".equals(environment.getActiveProfiles()[0])) {
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
}
return interceptor;
}
2. 自定义阻断逻辑(高级)
虽然 BlockAttackInnerInterceptor
不支持直接扩展,但可通过自定义 InnerInterceptor
实现:
@Component
public class CustomBlockInterceptor implements InnerInterceptor {
@Override
public void beforePrepare(StatementHandler sh, Statement stmt, int transactionTimeout) {
BoundSql boundSql = sh.getBoundSql();
String sql = boundSql.getSql().toLowerCase();
// 简单判断(不推荐,建议用 AST 解析)
if ((sql.contains("update") || sql.contains("delete"))
&& !sql.contains("where")) {
throw new RuntimeException("禁止执行无 WHERE 条件的 UPDATE/DELETE");
}
}
}
⚠️ 建议:优先使用官方
BlockAttackInnerInterceptor
,更稳定。
3. 结合日志监控
// 在全局异常处理器中记录
@ExceptionHandler(MybatisPlusException.class)
public ResponseEntity<?> handleMybatisPlusException(MybatisPlusException e) {
log.warn("SQL 阻断触发: {}", e.getMessage(), e);
return ResponseEntity.status(400).body("非法操作已被拦截");
}
六、最佳实践与性能优化
✅ 最佳实践
实践 | 说明 |
---|---|
✅ 生产环境强制开启 | 防止人为或程序错误导致数据灾难 |
✅ 结合代码审查 | 避免在代码中写 wrapper.apply("1=1") |
✅ 团队培训 | 让开发人员了解该插件的存在和作用 |
✅ 异常友好提示 | 返回“操作非法”而非技术异常 |
✅ 与 @TableLogic 配合 |
逻辑删除也应防止全表操作 |
✅ 定期审计日志 | 查看是否有频繁阻断,排查潜在风险 |
⚡ 性能优化建议
- ✅ 无性能瓶颈:解析 SQL 的 AST 成本极低,不影响吞吐量。
- ✅ 避免频繁调用:减少不必要的
update(null)
调用。 - ✅ 缓存预编译 SQL:MyBatis 本身已优化,无需额外处理。
七、原理深入:SQL 解析过程(简化版)
BlockAttackInnerInterceptor
内部大致流程:
void beforePrepare(StatementHandler sh, ...) {
String sql = sh.getBoundSql().getSql();
// 1. 使用 SQL 解析器(如 JSQLParser)解析 SQL
Statement stmt = CCJSqlParserUtil.parse(sql);
if (stmt instanceof Update || stmt instanceof Delete) {
Expression where = getWhere(stmt);
// 2. 判断 WHERE 是否为空或恒真
if (where == null || isAlwaysTrue(where)) {
throw new MybatisPlusException("prohibition of full table operation");
}
}
}
boolean isAlwaysTrue(Expression expr) {
return expr.toString().matches("(?i)(1=1|true|\\w+ IS NOT NULL)");
}
🔍 实际实现更复杂,会解析 AST 节点判断逻辑恒真性。
总结
BlockAttackInnerInterceptor
是 MyBatis-Plus 提供的数据库安全守护者,通过静态分析 SQL 语义,有效防止全表更新/删除等灾难性操作。
🚀 三步启用:
- 升级 MP 到 3.4.0+
- 配置
MybatisPlusInterceptor
并添加BlockAttackInnerInterceptor
- 生产环境强制开启,开发环境可选
掌握该插件,让你的系统在面对“手滑”或“恶意 SQL”时多一层坚实防护。适用于所有涉及数据修改的业务场景,尤其是金融、电商、后台管理系统等对数据一致性要求高的系统。