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 的值。

相关推荐
张某布响丸辣5 分钟前
SQL中的时间类型:深入解析与应用
java·数据库·sql·mysql·oracle
喜欢打篮球的普通人10 分钟前
rust模式和匹配
java·算法·rust
java小吕布23 分钟前
Java中的排序算法:探索与比较
java·后端·算法·排序算法
慢生活的人。29 分钟前
SpringSecurity+jwt+captcha登录认证授权总结
java·认证·rbac·权限·验证
向阳12181 小时前
LeetCode40:组合总和II
java·算法·leetcode
云空1 小时前
《InsCode AI IDE:编程新时代的引领者》
java·javascript·c++·ide·人工智能·python·php
慧都小妮子1 小时前
Spire.PDF for .NET【页面设置】演示:复制 PDF 文档中的页面
java·pdf·.net
Dr_eamboat1 小时前
【Java】枚举类映射
java·开发语言·python
李少兄1 小时前
解决 Spring Boot 中 `Ambiguous mapping. Cannot map ‘xxxController‘ method` 错误
java·spring boot·后端
matrixlzp1 小时前
Java 责任链模式 减少 if else 实战案例
java·设计模式