前言
- 从上一篇知道IllegalSQLInnerInterceptor插件可以拦截没带条件的全表扫描,但在项目中经常有1=1的条件,这种情况在IllegalSQLInnerInterceptor是不被拦截的
- 关于JSQLParser的官网学习参考https://github.com/JSQLParser/JSqlParser/wiki
- 下面主要参考IllegalSQLInnerInterceptor插件实现一个符合项目需求的插件SelectCheckInterceptor
3.1 全表查询排除1=1的条件
3.2 支持某些全表查询
Jsqlparser条件表达式
- 调式示例
@Test public void testParse() throws Exception { Statement stmt = CCJSqlParserUtil.parse("SELECT * FROM tab1 where 1 = 1"); Select select = (Select) stmt; PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); Expression where = plainSelect.getWhere(); }
- Expression where有多种实现,当1=1时,where为EqualsTo
2.1 当有多个条件时,where为AndExpression,并且可以通过leftExpression属性进行嵌套
恒等式 1 = 1 或 ‘1’ = ‘1’
- 当条件为where 1 = 1时,EqualsTo的leftExpression和rightExpression为LongValue
1.1 当条件为where ‘1’ = ‘1’ 时,EqualsTo的leftExpression和rightExpression为StringValue
- LongValue/StringValue 可以通过getValue方法获取其值
自定义查询防全表查询的插件
参考IllegalSQLInnerInterceptor的实现
-
仿照IllegalSQLInnerInterceptor实现自定义查询校验的插件
1.1 IllegalSQLInnerInterceptor继承JsqlParserSupport实现InnerInterceptor
1.2 查询时插件执行哪些方法,在MybatisPlusInterceptor#intercept可知
通过上面两副截图可以发现,在执行SQL时会调用两次Plugin#invoke。第一次是executor的形式,第二次是handler的形式 -
InnerInterceptor#willDoQuery 判断是否执行,默认为true
-
InnerInterceptor#beforeQuery 操作前置处理,默认为空
-
InnerInterceptor#beforePrepare 操作前置处理,在IllegalSQLInnerInterceptor中对sql做缓存处理
4.1 重点关注一下InterceptorIgnoreHelper#willIgnoreIllegalSql(因为有些小表可以全表扫描)
(1)通过下图发现,INTERCEPTOR_IGNORE_CACHE的key 可能是 mappedStatement 的 ID,也可能是 class 的 name
-
JsqlParserSupport#processSelect 处理查询,IllegalSQLInnerInterceptor对查询做分析校验
拦截忽略注解 @InterceptorIgnore
- 用法:在方法和类中加上@InterceptorIgnore注解
- 注解的初始化
2.1 mapper方法注解初始化:InterceptorIgnoreHelper#initSqlParserInfoCache(InterceptorIgnoreCache mapperAnnotation, String mapperClassName, Method method)
2.2 mapper类注解初始化:InterceptorIgnoreHelper#initSqlParserInfoCache(Class<?> mapperClass) - 构建拦截忽略缓存:InterceptorIgnoreHelper#buildInterceptorIgnoreCache
- 在 InnerInterceptor#beforePrepare判断是否忽略:InterceptorIgnoreHelper#willIgnoreIllegalSql
4.1 判断@InterceptorIgnore的illegalSql属性值,为true则忽略
4.2 如果直接用BaseMapper的getAll,但有些想支持全表查询,可以重写加上注解@InterceptorIgnore
实现防全表查询的插件
public class SelectCheckInterceptor extends JsqlParserSupport implements InnerInterceptor {
/**
* 缓存验证结果,提高性能
*/
private static final Set<String> cacheValidResult = new HashSet<>();
@Override
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
PluginUtils.MPStatementHandler mpStatementHandler = PluginUtils.mpStatementHandler(sh);
MappedStatement ms = mpStatementHandler.mappedStatement();
SqlCommandType sct = ms.getSqlCommandType();
//@InterceptorIgnore(illegalSql = "true") mapper类或方法上配此注解忽略校验
if (sct == SqlCommandType.INSERT || InterceptorIgnoreHelper.willIgnoreIllegalSql(ms.getId())) return;
BoundSql boundSql = mpStatementHandler.boundSql();
String originalSql = boundSql.getSql();
logger.debug("检查SQL是否合规,SQL:" + originalSql);
String md5Base64 = EncryptUtils.md5Base64(originalSql);
if (cacheValidResult.contains(md5Base64)) {
logger.debug("该SQL已验证,无需再次验证,,SQL:" + originalSql);
return;
}
parserSingle(originalSql, connection);
//缓存验证结果
cacheValidResult.add(md5Base64);
}
@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
Expression where = plainSelect.getWhere();
Assert.notNull(where, "非法SQL,必须要有where条件");
if(where instanceof EqualsTo){
Expression left = ((EqualsTo) where).getLeftExpression();
Expression right = ((EqualsTo) where).getRightExpression();
if(left.getClass().isAssignableFrom(right.getClass())){
if(left instanceof StringValue &&
((StringValue) left).getValue().equals(((StringValue) right).getValue())){
throw new MybatisPlusException("非法SQL,必须要有where条件");
}
if(left instanceof LongValue &&
((LongValue) left).getValue() == ((LongValue) right).getValue() ){
throw new MybatisPlusException("非法SQL,必须要有where条件");
}
}
}
}
}
小知识补充
Maven 父pom中dependencyManagement版本优先级高于传递依赖版本
-
父pom文件
<dependencyManagement> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> </dependencyManagement>
-
子模块的pom文件
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.4</version> </dependency>
-
此时该工程中mybatis-spring的版本将是1.3.0,即父pom dependencyManagement中的版本