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

一、使用方式

数据权限控制需要对查询出的数据进行筛选,对业务入侵最少的方式就是利用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

点赞
收藏

评论区

加载中...

相关推荐

微服务拆分治理最佳实践

随着业务快速发展,各种问题越来越明显,急需对系统进行微服务改造优化。经过思考,整体改造将分为三个阶段进行:数据库拆分、应用拆分、数据访问权限收口。

drools规则动态化实践

业务逻辑中经常会有一些冗长的判断,需要写特别多的ifelse,或者一些判断逻辑需要经常修改。这部分逻辑如果以java代码来实现,会面临代码规模控制不住,经常需要修改逻辑上线等多个弊端。这时候我们就需要集成规则引擎对这些判断进行线上化的管理。

Mybatis查询的时候BigDecimal类型的值查询失效的解决办法

最近在使用Mybatis查询的时候,使用了BigDecimal类型的值进行查询,在控制台通过打印的sql发现,查询条件并没有拼接上去,导致查询失败。为了演示还原这个过程,特意写了一个简单的演示项目:比如:我现在查询productprice字段大于0的数据,数据库的数据如下所示:mapper.xml中配置如下:javaselecti

Apache Sentry实战之旅(二)—— Sentry客户端使用

ApacheSentry虽然可以将HDFS、Hive与Impala三个组件的权限认证统一,但是只能按照给组授予角色的方式来进行授权,不能直接授权给组中的用户,显得不太灵活。有时候为了兼容已有大数据平台的授权体系,比如只使用Sentry控制Impala服务的权限,而不控制Hive和HDFS服务的权限,希望通过调用Sentry客

PostgreSQL下如何修改用户权限的介绍以及hook机制对超级用户的权限修改

要想修改PG的用户权限,那么首先要对PG权限控制做一下了解:PG的权限控制是针对到各个对象的。大家可以看一下,所有系统表(pg\_catalog下)几乎都会有aclitem\\数组类型的\\acl的字段,这就是对权限的标识。这里的标识情况如下:rolenamexxxxprivilegesgrantedtoarol

Hbase权限控制

Hbase权限配置、使用手册1Hbase权限控制简介Hbase的权限控制是通过AccessControllerCoprocessor协处理器框架实现的,可实现对用户的RWXCA的权限控制。2配置配置hbasesite.xmlCM主页→点击hbase(进入Hbase