Mybatis Plus 数据权限插件坑点

原创 rookie 简迅云笔记 2023-11-17 12:05 发表于广东

Mybatis Plus 数据权限插件坑点

Mybatis Plus 插件版本:3.5.3.1

插件作用:通过拼接 SQL 的方式给 where 添加查询条件达到数据隔离的效果。

插件集成
 
  1. @Slf4j

  2. public class MyDataPermissionHandler implements DataPermissionHandler {

  3. /**

  4. * 获取数据权限 SQL 片段

  5. *

  6. * @param where 待执行 SQL Where 条件表达式

  7. * @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法

  8. * @return JSqlParser 条件表达式

  9. */

  10. @Override

  11. public Expression getSqlSegment(Expression where, String mappedStatementId) {

  12. return where;

  13. }

  14. }

  15. public MybatisPlusInterceptor mybatisPlusInterceptor() {

  16. DataPermissionInterceptor dataPermissionInterceptor = new DataPermissionInterceptor();

  17. dataPermissionInterceptor.setDataPermissionHandler(new MyDataPermissionHandler());

  18. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

  19. interceptor.addInnerInterceptor(dataPermissionInterceptor);

  20. return interceptor;

  21. }

注解控制
 
  1. @Mapper

  2. @InterceptorIgnore(dataPermission = "1")

  3. public interface XXXMapper extends BaseMapper<XXX> {

  4. @InterceptorIgnore(dataPermission = "0")

  5. @Override

  6. <P extends IPage<XXX>> P selectPage(P page,@Param(Constants.WRAPPER) Wrapper<XXX> queryWrapper);

  7. }

1、true、on 忽略权限过滤、0、fales、off 启用权限过滤。

getSqlSegment 方法作用

看方法暴露出来的参数可以看出能拿到 SQL 中的 where sql 片段,然后自己拼接 sql。比如:

 
  1. ItemsList itemsList = new ExpressionList(list.stream()

  2. .map(StringValue::new)

  3. .collect(Collectors.toList()));

  4. InExpression inExpression = new InExpression(new Column("id"),

  5. itemsList);

  6. return new AndExpression(where, inExpression);

  7. // or

  8. String sqlSegment = "username='123' or userId IN (1,2,3)";

  9. Expression sqlSegmentExpression = CCJSqlParserUtil.parseCondExpression(sqlSegment);

坑点

忽略权限过滤后配置 MyDataPermissionHandler 的 getSqlSegment 都不执行了。

刚开始以为是配置出问题插件不执行了,然后找到插件的执行位置 com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor#intercept

 
  1. for (InnerInterceptor query : interceptors) {

  2. if (!query.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql)) {

  3. return Collections.emptyList();

  4. }

  5. query.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);

  6. }

发现插件还是照常执行的,后来在 com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor#beforeQuery 发现有个注解的值处理:

 
  1. if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {

  2. return;

  3. }

我配置成忽略权限过滤也就是 true 的时候当前方法就直接中断执行不会往 getSqlSegment 去执行了(总感觉注解值怪怪的有点绕……)。

问题查找过程

假设我是在不知道源码具体执行位置的情况下看看如何去发现这个问题。

首先问题出现的情况是配置注解后我的配置类就不执行了,那么在方法中打个断点,把注解去掉,先让方法执行拿到方法的调用栈。【1】

这些调用栈从上到下是入口到出口的方法调用顺序,从调用栈中每个方法点进去就很容易找到插件的执行位置,调用的入口。

然后在入口中打一个断点,把注解的注释放开,然后一步步步过,就能发现出问题的地方了。

注解扩展

在方法中只能通过 InterceptorIgnoreHelper.willIgnoreDataPermission(mappedStatementId); 方法获取注解值是启用开始禁用的状态,注解值我们是拿不到的,看下这个方法的源码想着把他判断的逻辑给拿出来,但是发现它的值是 private 不给用,虽然不给用但是我们也是可以强制扣出里面的值,这时候可以用反射把他的值给拿出来。

 
  1. private IgnoreStrategy getStrategy(String id) {

  2. IgnoreStrategy ignoreStrategy = null;

  3. try {

  4. ThreadLocal<IgnoreStrategy> ignoreStrategyThreadLocal = null;

  5. Map<String, IgnoreStrategy> ignoreStrategyCache = null;

  6. boolean search1 = false;

  7. boolean search2 = false;

  8. Field[] declaredFields = InterceptorIgnoreHelper.class.getDeclaredFields();

  9. for (Field declaredField : declaredFields) {

  10. if (search1 && search2){

  11. break;

  12. }

  13. declaredField.setAccessible(true);

  14. if (declaredField.getType() == ThreadLocal.class && "IGNORE_STRATEGY_LOCAL".equals(declaredField.getName())) {

  15. ignoreStrategyThreadLocal = (ThreadLocal<IgnoreStrategy>) declaredField.get(InterceptorIgnoreHelper.class);

  16. search1 = true;

  17. continue;

  18. }

  19. if (declaredField.getType() == Map.class && "IGNORE_STRATEGY_CACHE".equals(declaredField.getName())) {

  20. ignoreStrategyCache = (Map<String, IgnoreStrategy>) declaredField.get(InterceptorIgnoreHelper.class);

  21. search2 = true;

  22. }

  23. }

  24. if (ignoreStrategyThreadLocal == null || ignoreStrategyCache == null) {

  25. return null;

  26. }

  27. ignoreStrategy = ignoreStrategyThreadLocal.get();

  28. if (null == ignoreStrategy) {

  29. ignoreStrategy = ignoreStrategyCache.get(id);

  30. }

  31. if (ignoreStrategy == null && id.endsWith("!selectKey")) {

  32. ignoreStrategy = (IgnoreStrategy) ignoreStrategyCache.get(id.substring(0, id.length() - "!selectKey".length()));

  33. }

  34. if (ignoreStrategy == null) {

  35. ignoreStrategy = (IgnoreStrategy) ignoreStrategyCache.get(id.substring(0, id.lastIndexOf(".")));

  36. }

  37. } catch (IllegalAccessException e) {

  38. e.printStackTrace();

  39. }

  40. return ignoreStrategy;

  41. }

当然拿到 IgnoreStrategy 是被处理过后的,我们可以获取 others 的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值