Mybatis Plus 数据权限插件坑点

2023-11-20 14:41:57 浏览数 (1)

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。比如:

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

代码语言:text复制
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {  
            return;  
        }  

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

问题查找过程

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

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

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

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

注解扩展

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

代码语言:text复制
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 的值。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞