首页 > 其他分享 >使用mybatis切片实现数据权限控制

使用mybatis切片实现数据权限控制

时间:2024-07-08 19:09:24浏览次数:14  
标签:切片 apache ms import mybatis org 权限 class

 

数据权限控制需要对查询出的数据进行筛选,对业务入侵最少的方式就是利用mybatis或者数据库连接池的切片对已有业务的sql进行修改。切片逻辑完成后,仅需要在业务中加入少量标记代码,就可以实现对数据权限的控制。这种修改方式,对老业务的逻辑没有入侵或只有少量入侵,基本不影响老业务的逻辑和可读性;对新业务,业务开发人员无需过多关注权限问题,可以集中精力处理业务逻辑。

由于部门代码中使用的数据库连接池种类较多,不利于切片控制逻辑的快速完成,而sql拼接的部分基本只有mybatis和java字符串直接拼接两种方式,因此使用mybatis切片的方式来完成数据权限控制逻辑。在mybatis的mapper文件的接口上添加注解,注解中写明需要控制的权限种类、要控制的表名、列名即可控制接口的数据权限。

 


 

由于mybatis的mapper文件中的同一接口在多个地方被调用,有的需要控制数据权限,有的不需要,因此增加一种权限控制方式:通过ThreadLocal传递权限控制规则来控制当前sql执行时控制数据权限。

 


 

权限控制规则格式如下:

限权规则code1(表名1.字段名1,表名2.字段名2);限权规则code2(表名3.字段名3,表名4.字段名4)

例如:enterprise(channel.enterprise_code);account(table.column);channel(table3.id)

上下文传递工具类如下所示,使用回调的方式传递ThreadLocal可以防止使用者忘记清除上下文。

 

public class DataAuthContextUtil {
    /**
     * 不方便使用注解的地方,可以直接使用上下文设置数据规则
     */
    private static ThreadLocal<String> useDataAuth = new ThreadLocal<>();

    /**
     * 有的sql只在部分情况下需要使用数据权限限制

     * 上下文和注解中均可设置数据权限规则,都设置时,上下文中的优先
     *
     * @param supplier
     */
    public static <T> T executeSqlWithDataAuthRule(String rule, Supplier<T> supplier) {
        try {
            useDataAuth.set(rule);
            return supplier.get();
        } finally {
            useDataAuth.remove();
        }
    }

    /**
     * 获取数据权限标志
     *
     * @return
     */
    public static String getUseDataAuthRule() {
        return useDataAuth.get();
    }
}

二、切片实现流程

 


 

三、其他技术细节

(1)在切面中获取原始sql

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import reactor.util.function.Tuple2;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Component
@Intercepts({
//        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
@Slf4j
public class DataAuthInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
            String sql = boundSql.getSql();
        } catch (Exception e) {
            log.error("数据权限添加出错,当前sql未加数据权限限制!", e);
            throw e;
        }
        return invocation.proceed();
    }
}

(2)将权限项加入原始sql中

使用druid附带的ast解析功能修改sql,代码如下

/**
     * 权限限制写入sql
     *
     * @param sql
     * @param tableAuthMap key:table value1:column value2:values权限项
     * @return
     */
    public static StringBuilder addAuthLimitToSql(String sql, Map<String, Tuple2<String, Set<String>>> tableAuthMap) {
        List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, "mysql");
        StringBuilder authSql = new StringBuilder();
        for (SQLStatement stmt : stmtList) {
            stmt.accept(new MySqlASTVisitorAdapter() {
                @Override
                public boolean visit(MySqlSelectQueryBlock x) {
                    SQLTableSource from = x.getFrom();
                    Set<String> tableList = new HashSet<>();
                    getTableList(from, tableList);
                    for (String tableName : tableList) {
                        if (tableAuthMap.containsKey(tableName)) {
                            x.addCondition(tableName + "in (...略)");
                        }
                    }
                    return true;
                }
            });
            authSql.append(stmt);
        }
        return authSql;
    }
    
    private static void getTableList(SQLTableSource from, Set<String> tableList) {
        if (from instanceof SQLExprTableSource) {
            SQLExprTableSource tableSource = (SQLExprTableSource) from;
            String name = tableSource.getTableName().replace("`", "");
            tableList.add(name);
            String alias = tableSource.getAlias();
            if (StringUtils.isNotBlank(alias)) {
                tableList.add(alias.replace("`", ""));
            }
        } else if (from instanceof SQLJoinTableSource) {
            SQLJoinTableSource joinTableSource = (SQLJoinTableSource) from;
            getTableList(joinTableSource.getLeft(), tableList);
            getTableList(joinTableSource.getRight(), tableList);
        } else if (from instanceof SQLSubqueryTableSource) {
            SQLSubqueryTableSource tableSource = (SQLSubqueryTableSource) from;
            tableList.add(tableSource.getAlias().replace("`", ""));
        } else if (from instanceof SQLLateralViewTableSource) {
            log.warn("SQLLateralView不用处理");
        } else if (from instanceof SQLUnionQueryTableSource) {
            //union 不需要处理
            log.warn("union不用处理");
        } else if (from instanceof SQLUnnestTableSource) {
            log.warn("Unnest不用处理");
        } else if (from instanceof SQLValuesTableSource) {
            log.warn("Values不用处理");
        } else if (from instanceof SQLWithSubqueryClause) {
            log.warn("子查询不用处理");
        } else if (from instanceof SQLTableSourceImpl) {
            log.warn("Impl不用处理");
        }
    }
}

(3)将修改过后的sql写回mybatis

        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]);
        // 组装 MappedStatement
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), new MySqlSource(boundSql), ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        MappedStatement newMappedStatement = builder.build();
        MetaObject metaObject = MetaObject.forObject(newMappedStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        metaObject.setValue("sqlSource.boundSql.sql", newSql);
        invocation.getArgs()[0] = newMappedStatement;

 

参考文章: https://blog.csdn.net/e_anjing/article/details/79102693

标签:切片,apache,ms,import,mybatis,org,权限,class
From: https://www.cnblogs.com/Jcloud/p/18290570

相关文章

  • 02.Slice 切片
    切片(Slice)是Go语言中比数组更为灵活和强大的数据结构。切片本质上是对数组的一个视图,可以动态调整大小。下面详细介绍切片的定义、初始化、操作和使用注意事项。切片的定义与初始化切片的定义语法为:[]T,其中T表示切片元素的类型。切片的初始化可以通过数组字面量、内置函数m......
  • 使用MybatisPlus为字段值进行自动填充
    使用MybatisPlus为字段值进行自动填充保存或更新数据时,前端通常不会传入诸如isDeleted、createTime、updateTime这三个字段,因此我们需要手动赋值。但是数据库中几乎每张表都有上述字段,所以手动去赋值就显得有些繁琐。为简化上述操作,我们可采取以下措施。首先是is_deleted字段在......
  • MybatisPlus使用分页功能
    MybatisPlus使用分页功能分页查询是一个很常见的需求,故Mybatis-Plus提供了一个分页插件,使用它可以十分方便的完成分页查询。下面介绍Mybatis-Plus分页插件的用法,详细信息可参考[官方文档](分页插件|MyBatis-Plus(baomidou.com))首先为分页编写一个配置类:@Configurationpub......
  • MybatisPlus配置逻辑删除
    MybatisPlus配置逻辑删除逻辑删除,可以方便地实现对数据库记录的逻辑删除而不是物理删除。逻辑删除是指通过更改记录的状态或添加标记字段来模拟删除操作,从而保留了删除前的数据,便于后续的数据分析和恢复。物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数......
  • 从 0 实现一个 vue3 的权限指令 v-permission
    在开发过程中会经常遇到一些权限控制,比如路由的权限控制、按钮的权限控制......
  • mybatis中resultType为map时,null值的字段不返回
    <selectid="getUser"parameterType="map"resultType="map">SELECTid,name,addrFROMuserWHEREid=#{id}</select>以上查询,当addr字段值在数据库中为null时,返回的结果如下:1......
  • Linux系统权限控制详解(理论+实战)
    一、权限概述Linux权限控制是指对文件和目录的访问权限进行限制,以保护系统安全和数据完整性。其作用包括以下几个方面:保护系统安全:Linux系统中许多重要的配置文件和程序文件只有管理员才有权限访问,一般用户不能修改或删除这些文件,以防止系统被破坏。保护用户数据:每个用户的家......
  • Mybatis-plus
    Mapper接口:定义一个Mapper接口,继承自MyBatis-Plus的 BaseMapper 接口,这样你就可以使用MyBatis-Plus提供的各种方法了。java复制代码 packagebjdx.example.mapper;   importbjdx.example.entity.User; importcom.baomidou.mybatisplus.c......
  • VMware vCenter Server 7.0U3r 下载 - 修复堆溢出 (远程执行代码) 和本地权限提升漏洞
    VMwarevCenterServer是一款高级服务器管理软件,提供了一个集中式平台来控制vSphere环境,以实现跨混合云的可见性。简化且高效的服务器管理什么是vCenterServer?实现集中式可见性、简化且高效的大规模管理,以及在整个混合云中的可延展性,所有这一切,均可通过单一控制台来实现......
  • Mybatis源码(4)一级缓存和二级缓存
    1、目标本文的主要目标是探究Mybatis源码中的一级缓存和二级缓存,先分析标签作用,然后分析一级缓存和二级缓存的源码2、一级缓存的标签2.1cacheEnabled标签cacheEnabled是控制二级缓存是否可以使用,默认值是true表示二级缓存可以使用(不表示开启了二级缓存),但是一级缓存始......