Mybatis Plus 数据权限插件坑点

Mybatis Plus 数据权限插件坑点

Mybatis Plus 插件版本:3.5.3.1

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

插件集成

typescript 复制代码
@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;  
}  

注解控制

less 复制代码
@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。比如:

ini 复制代码
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

scss 复制代码
 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 发现有个注解的值处理:

kotlin 复制代码
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {  
            return;  
        }  

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

问题查找过程

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

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

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

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

注解扩展

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

ini 复制代码
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 的值。

相关推荐
wangbing11251 分钟前
下划线数字字面量
java
星间都市山脉11 分钟前
RecoveryUI 圆角屏安全边距配置及代码调用链
android·java·linux·windows·ubuntu
sunshine22 girl14 分钟前
Java学习五 面向对象高级5 内部类4-匿名内部类(重点)
java·学习
java_nnnn18 分钟前
Spring Boot 快速上手:从 Maven 到第一个 Controller
java·spring·java-ee·maven
夕除23 分钟前
redis--集群
java·redis
蜗牛互联网24 分钟前
OLMo-core 3的token gerrymandering提醒:MoE路由要按时间窗验收
java·人工智能·wpf
奇牙coding27 分钟前
Codex C接 配置教程:的 字段从迁移时必须写完整后缀,填旧值或省略 会静默回退默认模型
java·c语言·数据库·ai
卓怡学长36 分钟前
w193基于springboot“乐通黄骅”电动车智能充电服务小程序
java·spring boot·spring·小程序·intellij-idea
谢亮_vipxieliang1 小时前
GC 入门:G1 与 ZGC 怎么选
java·jvm·算法
Cc.Y5 小时前
Java零基础入门:字符串深度掌握——从基础API到StringBuilder性能优化
java·开发语言·性能优化