原创 rookie 简迅云笔记 2023-11-17 12:05 发表于广东
Mybatis Plus 数据权限插件坑点
Mybatis Plus 插件版本:3.5.3.1
插件作用:通过拼接 SQL 的方式给 where 添加查询条件达到数据隔离的效果。
插件集成
-
@Slf4j
-
public class MyDataPermissionHandler implements DataPermissionHandler {
-
/**
-
* 获取数据权限 SQL 片段
-
*
-
* @param where 待执行 SQL Where 条件表达式
-
* @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
-
* @return JSqlParser 条件表达式
-
*/
-
@Override
-
public Expression getSqlSegment(Expression where, String mappedStatementId) {
-
return where;
-
}
-
}
-
public MybatisPlusInterceptor mybatisPlusInterceptor() {
-
DataPermissionInterceptor dataPermissionInterceptor = new DataPermissionInterceptor();
-
dataPermissionInterceptor.setDataPermissionHandler(new MyDataPermissionHandler());
-
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
-
interceptor.addInnerInterceptor(dataPermissionInterceptor);
-
return interceptor;
-
}
注解控制
-
@Mapper
-
@InterceptorIgnore(dataPermission = "1")
-
public interface XXXMapper extends BaseMapper<XXX> {
-
@InterceptorIgnore(dataPermission = "0")
-
@Override
-
<P extends IPage<XXX>> P selectPage(P page,@Param(Constants.WRAPPER) Wrapper<XXX> queryWrapper);
-
}
1、true、on 忽略权限过滤、0、fales、off 启用权限过滤。
getSqlSegment 方法作用
看方法暴露出来的参数可以看出能拿到 SQL 中的 where sql 片段,然后自己拼接 sql。比如:
-
ItemsList itemsList = new ExpressionList(list.stream()
-
.map(StringValue::new)
-
.collect(Collectors.toList()));
-
InExpression inExpression = new InExpression(new Column("id"),
-
itemsList);
-
return new AndExpression(where, inExpression);
-
// or
-
String sqlSegment = "username='123' or userId IN (1,2,3)";
-
Expression sqlSegmentExpression = CCJSqlParserUtil.parseCondExpression(sqlSegment);
坑点
忽略权限过滤后配置 MyDataPermissionHandler 的 getSqlSegment 都不执行了。
刚开始以为是配置出问题插件不执行了,然后找到插件的执行位置 com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor#intercept
-
for (InnerInterceptor query : interceptors) {
-
if (!query.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql)) {
-
return Collections.emptyList();
-
}
-
query.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
-
}
发现插件还是照常执行的,后来在 com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor#beforeQuery
发现有个注解的值处理:
-
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
-
return;
-
}
我配置成忽略权限过滤也就是 true 的时候当前方法就直接中断执行不会往 getSqlSegment 去执行了(总感觉注解值怪怪的有点绕……)。
问题查找过程
假设我是在不知道源码具体执行位置的情况下看看如何去发现这个问题。
首先问题出现的情况是配置注解后我的配置类就不执行了,那么在方法中打个断点,把注解去掉,先让方法执行拿到方法的调用栈。【1】
这些调用栈从上到下是入口到出口的方法调用顺序,从调用栈中每个方法点进去就很容易找到插件的执行位置,调用的入口。
然后在入口中打一个断点,把注解的注释放开,然后一步步步过,就能发现出问题的地方了。
注解扩展
在方法中只能通过 InterceptorIgnoreHelper.willIgnoreDataPermission(mappedStatementId);
方法获取注解值是启用开始禁用的状态,注解值我们是拿不到的,看下这个方法的源码想着把他判断的逻辑给拿出来,但是发现它的值是 private 不给用,虽然不给用但是我们也是可以强制扣出里面的值,这时候可以用反射把他的值给拿出来。
-
private IgnoreStrategy getStrategy(String id) {
-
IgnoreStrategy ignoreStrategy = null;
-
try {
-
ThreadLocal<IgnoreStrategy> ignoreStrategyThreadLocal = null;
-
Map<String, IgnoreStrategy> ignoreStrategyCache = null;
-
boolean search1 = false;
-
boolean search2 = false;
-
Field[] declaredFields = InterceptorIgnoreHelper.class.getDeclaredFields();
-
for (Field declaredField : declaredFields) {
-
if (search1 && search2){
-
break;
-
}
-
declaredField.setAccessible(true);
-
if (declaredField.getType() == ThreadLocal.class && "IGNORE_STRATEGY_LOCAL".equals(declaredField.getName())) {
-
ignoreStrategyThreadLocal = (ThreadLocal<IgnoreStrategy>) declaredField.get(InterceptorIgnoreHelper.class);
-
search1 = true;
-
continue;
-
}
-
if (declaredField.getType() == Map.class && "IGNORE_STRATEGY_CACHE".equals(declaredField.getName())) {
-
ignoreStrategyCache = (Map<String, IgnoreStrategy>) declaredField.get(InterceptorIgnoreHelper.class);
-
search2 = true;
-
}
-
}
-
if (ignoreStrategyThreadLocal == null || ignoreStrategyCache == null) {
-
return null;
-
}
-
ignoreStrategy = ignoreStrategyThreadLocal.get();
-
if (null == ignoreStrategy) {
-
ignoreStrategy = ignoreStrategyCache.get(id);
-
}
-
if (ignoreStrategy == null && id.endsWith("!selectKey")) {
-
ignoreStrategy = (IgnoreStrategy) ignoreStrategyCache.get(id.substring(0, id.length() - "!selectKey".length()));
-
}
-
if (ignoreStrategy == null) {
-
ignoreStrategy = (IgnoreStrategy) ignoreStrategyCache.get(id.substring(0, id.lastIndexOf(".")));
-
}
-
} catch (IllegalAccessException e) {
-
e.printStackTrace();
-
}
-
return ignoreStrategy;
-
}
当然拿到 IgnoreStrategy 是被处理过后的,我们可以获取 others 的值。