一套完整的系统权限需要支持功能权限和数据权限。前面介绍了系统通过RBAC权限模型实现功能权限控制。在这里,我们将通过扩展Mybatis-plus的插件Datapermisioninterceptor来实现数据权限控制。
简单介绍一下,所谓功能权限,顾名思义,是指用户在系统中控制哪些功能,数据权限是指用户在系统中可以访问哪些数据的权限控制。数据权限分为行级数据权限和列级数据权限。
数据权限的基本概念:
- 行级数据权限:以表结构为描述对象,用户有哪些数据权限,表示数据库数据权限,如根据部门,行数据属于部门,用户只有部门数据权限,所以用户有行数据权限。
- 列数据权限:以表结构为描述对象,用户可能只有一部分字段,如银行卡、手机号码等重要信息只有高级用户可以查询,一些基本信息,普通用户可以查询,不同的用户角色有不同的数据权限。
实现方式:
- 行级数据权限: 对行级数据权限进行细分,以角色为标识的数据权限分为: 1、只能查看自己的数据; 2、只能查看本部门的数据; 3、只能查看本部门和子部门的数据; 4、可查看所有部门的数据; 以用户为标识的数据权限,分为: 5、具有相同功能的角色权限具有不同部门的数据权限; 6、不同的角色权限有不同部门的数据权限。
第1/2/3/4类的实现模式需要在角色列表中配置角色的数据权限,以及角色在某个接口中拥有哪些数据权限。 第五种实现方式需要在用户列表中配置,并将多个不同的部门分配给用户。 第六类的实现方法比较复杂。目前市场上的解决方案大多是: 1、在登录时,判断用户是否有多个部门。如果存在,请先让用户选择自己的部门,登录后只操作所选部门的权限; 2、为不同部门创建不同的用户和角色,登录时,选择相应的帐户进行登录。 个人更倾向于第二种方式,因为他们坚持复杂的系统简化,试图以低耦合的方式实现复杂功能的概念,因为:
1、降低系统实现的复杂性,判断越复杂,越容易出现问题,不仅在开发过程中,而且在后续系统的扩展和更新过程中。 2、对于工作量的选择,一个人拥有多个部门不同权限的方式是一种常见的功能,但并不常见,也就是说,在一个企业中,同一用户是业务部门经理和财务部门经理并不常见,更多的是全职的。这里要区分第五类。比如你是业务部门经理,可能会管理多个部门,属于权限一致,只有多个部门权限,属于第五类。另一个例子是总经理,他可能会看到所有的业务和财务数据都属于第四类。 因此,用户登录后不会选择部门来判断数据权限。- 等级数据权限: 列级数据权限的实现主要是针对角色可以看到的字段,没有用户给他特定的字段。在这种情况下,可以单独建立一个角色,并尝试使用RBAC来实现它,而不是使用户直接与数据权限相关。列级数据权限不仅要考虑后台取数据的问题,还要考虑在界面上显示时,如果是表格,则无权列需要根据数据权限来判断是否显示。在这里,应考虑配置界面。角色配置时,需要分为行级数据权限和列级数据权限进行不同的配置:行级数据权限应配置需要数据权限控制的接口、数据权限类型(上述1234);除上述配置外,列级数据权限还需要配置可访问的字段或排除访问的字段。
CREATE TABLE `t_sys_data_permission_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT “租户id”, `user_id` bigint(20) NOT NULL COMMENT 用户id, `organization_id` bigint(20) NOT NULL COMMENT 机构id, `status` tinyint(2) NULL DEFAULT 1 COMMENT '状态 0禁用,1 启用,', `create_time` datetime(0) NULL DEFAULT NULL COMMENT 创造时间, `creator` bigint(20) NULL DEFAULT NULL COMMENT 创造者, `update_time` datetime(0) NULL DEFAULT NULL COMMENT 更新时间, `operator` bigint(20) NULL DEFAULT NULL COMMENT "更新者", `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '1:删除 0:不删除', PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `t_sys_data_permission_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT “租户id”, `resource_id` bigint(20) NOT NULL DEFAULT 0 COMMENT 功能权限id, `data_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT "数据权限名称", `data_mapper_function` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 与数据权限对应的mapper方法全路径, `data_table_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4__general_ci NULL DEFAULT NULL COMMENT 要做数据权限主表, `data_table_alias` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 要做数据权限表的别名, `data_column_exclude` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 要排除数据权限的字段, `data_column_include` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4__general_ci NULL DEFAULT NULL COMMENT 要保留数据权限的字段, `inner_table_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 数据权限表,默认t_sys_organization', `inner_table_alias` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 数据权限表的别名,默认organization, `data_permission_type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT ‘数据权限类型:1只能查看自己 只能查看本部门 33只能检查本部门和子部门 所有数据都可以查看, `custom_expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 自定义数据权限(增加) where条件), `status` tinyint(2) NOT NULL DEFAULT 1 COMMENT '状态 0禁用,1 启用,', `comments` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', `create_time` datetime(0) NULL DEFAULT NULL COMMENT 创造时间, `creator` bigint(20) NULL DEFAULT NULL COMMENT “创建者”, `update_time` datetime(0) NULL DEFAULT NULL COMMENT 更新时间, `operator` bigint(20) NULL DEFAULT NULL COMMENT "更新者", `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '1:删除 0:不删除', PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = “数据权限配置表” ROW_FORMAT = DYNAMIC;
缓存数据权限(Redis)设计:- Redis Key:多租户模式:auth:tenant:data:permission:0(租户):mapper_Mapper全路径_type_数据权限类型普通模式:auth:data:permission:mapper_Mapper全路径_type_数据权限类型
- Redis Value:Datapermisionentity配置存储角色分配 组装SQL时,数据权限插件首先通过前缀匹配查询maper的statementid是否在缓存中。如果存在,取出当前用户的数据权限类型,组装带有数据权限类型的datapermision缓存Key,数据权限配置从缓存中取出。在设计角色时,除了为角色设置功能权限外,还需要设置数据权限类型。角色的数据权限类型只能单独选择(1) 只能查看本部门 33只能检查本部门和子部门 4可查看所有数据5的自定义。
- 由于Datapermisioninterceptor默认不支持修改selectitems,因此无法实现列级数据权限,因此Datapermisioninterceptor自定义扩展,使其支持列级权限扩展
@Data@NoArgsConstructor@AllArgsConstructor@ToString(callSuper = true)@EqualsAndHashCode(callSuper = true)public class GitEggDataPermissionInterceptor extends DataPermissionInterceptor { private GitEggDataPermissionHandler dataPermissionHandler; public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { if (!InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId())); } } protected void processSelect(Select select, int index, String sql, Object obj) { SelectBody selectBody = select.getSelectBody(); if (selectBody instanceof PlainSelect) { PlainSelect plainSelect = (PlainSelect)selectBody; this.processDataPermission(plainSelect, (String)obj); } else if (selectBody instanceof SetOperationList) { SetOperationList setOperationList = (SetOperationList)selectBody; List<SelectBody> selectBodyList = setOperationList.getSelects(); selectBodyList.forEach((s) -> { PlainSelect plainSelect = (PlainSelect)s; this.processDataPermission(plainSelect, (String)obj); }); } } protected void processDataPermission(PlainSelect plainSelect, String whereSegment) { this.dataPermissionHandler.processDataPermission(plainSelect, whereSegment); }}
- DatapermisionHandler数据权限控制自定义
@Component@RequiredArgsConstructor(onConstructor_ = @Autowired)public class GitEggDataPermissionHandler implements DataPermissionHandler { @Value(("${tenant.enable}")) private Boolean enable; /** * 默认关闭注解方法,这里只说明一种实现方法,实际使用时,使用配置的方式 */ @Value(("${data-permission.annotation-enable}")) private Boolean annotationEnable = false; private final RedisTemplate redisTemplate; public void processDataPermission(PlainSelect plainSelect, String mappedStatementId) { try { GitEggUser loginUser = GitEggAuthUtils.getCurrentUser(); // 1 当有数据权限配置时,判断用户是否有数据权限控制 if (ObjectUtils.isNotEmpty(loginUser) && CollectionUtils.isNotEmpty(loginUser.getDataPermissionTypeList())) { // 1 sql根据系统配置的数据权限组装 StringBuffer statementSb = new StringBuffer(); if (enable) { statementSb.append(DataPermissionConstant.TENANT_DATA_PERMISSION_KEY).append(loginUser.getTenantId()); } else { statementSb.append(DataPermissionConstant.DATA_PERMISSION_KEY); } String dataPermissionKey = statementSb.toString(); StringBuffer statementSbt = new StringBuffer(DataPermissionConstant.DATA_PERMISSION_KEY_MAPPER); statementSbt.append(mappedStatementId).append(DataPermissionConstant.DATA_PERMISSION_KEY_TYPE); String mappedStatementIdKey = statementSbt.toString(); DataPermissionEntity dataPermissionEntity = null; for (String dataPermissionType: loginUser.getDataPermissionTypeList()) { String dataPermissionUserKey = mappedStatementIdKey + dataPermissionType; dataPermissionEntity = (DataPermissionEntity) redisTemplate.boundHashOps(dataPermissionKey).get(dataPermissionUserKey); if (ObjectUtils.isNotEmpty(dataPermissionEntity)) { break; } } // mappedstatementid是否配置数据权限 if (ObjectUtils.isNotEmpty(dataPermissionEntity)) { dataPermissionFilter(loginUser, dataPermissionEntity, plainSelect); } ///默认不打开注释,因为每次查询都会影响性能,直接选择使用配置来实现数据权限 else if(annotationEnable) { // 2 sql根据注释的数据权限组装 Class<?> clazz = Class.forName(mappedStatementId.substring(GitEggConstant.Number.ZERO, mappedStatementId.lastIndexOf(StringPool.DOT))); String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringPool.DOT) + GitEggConstant.Number.ONE); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { //当有多种方法时,可以获得此方法 DataPermission[] annotations = method.getAnnotationsByType(DataPermission.class); if (ObjectUtils.isNotEmpty(annotations) && method.getName().equals(methodName)) { for (DataPermission dataPermission : annotations) { String dataPermissionType = dataPermission.dataPermissionType(); for (String dataPermissionUser : loginUser.getDataPermissionTypeList()) { if (ObjectUtils.isNotEmpty(dataPermission) && StringUtils.isNotEmpty(dataPermissionType) && dataPermissionUser.equals(dataPermissionType)) { DataPermissionEntity dataPermissionEntityAnnotation = annotationToEntity(dataPermission); dataPermissionFilter(loginUser, dataPermissionEntityAnnotation, plainSelect); break; } } } } } } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 构建过滤条件 * * @param user 目前登录用户 * @param plainSelect plainSelect * @return 施工后查询条件 */ public static void dataPermissionFilter(GitEggUser user, DataPermissionEntity dataPermissionEntity, PlainSelect plainSelect) { Expression expression = plainSelect.getWhere(); String dataPermissionType = dataPermissionEntity.getDataPermissionType(); String dataTableName = dataPermissionEntity.getDataTableName(); String dataTableAlias = dataPermissionEntity.getDataTableAlias(); String innerTableName = StringUtils.isNotEmpty(dataPermissionEntity.getInnerTableName()) ? dataPermissionEntity.getInnerTableName(): DataPermissionConstant.DATA_PERMISSION_TABLE_NAME; String innerTableAlias = StringUtils.isNotEmpty(dataPermissionEntity.getInnerTableAlias()) ? dataPermissionEntity.getInnerTableAlias() : DataPermissionConstant.DATA_PERMISSION_TABLE_ALIAS_NAME; List<String> organizationIdList = user.getOrganizationIdList(); // 列级数据权限 String dataColumnExclude = dataPermissionEntity.getDataColumnExclude(); String dataColumnInclude = dataPermissionEntity.getDataColumnInclude(); List<String> includeColumns = new ArrayList<>(); List<String> excludeColumns = new ArrayList<>(); // 只包含这些字段,即不是这些字段,直接删除 if (StringUtils.isNotEmpty(dataColumnInclude)) { includeColumns = Arrays.asList(dataColumnInclude.split(StringPool.COMMA)); } // 这些字段需要排除 if (StringUtils.isNotEmpty(dataColumnExclude)) { excludeColumns = Arrays.asList(dataColumnExclude.split(StringPool.COMMA)); } List<SelectItem> selectItems = plainSelect.getSelectItems(); List<SelectItem> removeItems = new ArrayList<>(); if (CollectionUtils.isNotEmpty(selectItems) && (CollectionUtils.isNotEmpty(includeColumns)
|| CollectionUtils.isNotEmpty(excludeColumns))) { for (SelectItem selectItem : selectItems) { // 其他类型的selectitem暂时不处理 if (selectItem instanceof SelectExpressionItem) { SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem; Alias alias = selectExpressionItem.getAlias(); if ((CollectionUtils.isNotEmpty(includeColumns) && !includeColumns.contains(alias.getName())) || (!CollectionUtils.isEmpty(excludeColumns) && excludeColumns.contains(alias.getName()))) { removeItems.add(selectItem); } } else if (selectItem instanceof AllTableColumns) { removeItems.add(selectItem); } } if (CollectionUtils.isNotEmpty(removeItems)) { selectItems.removeAll(removeItems); plainSelect.setSelectItems(selectItems); } } // 行级数据权限 // 查询用户机构和子机构的数据,这是通过查询where条件添加子来实现的。这种实现方式的优点是不需要判断Update、Insert还是Select,这些都是通用的,缺点是性能问题。 if (DataPermissionTypeEnum.DATA_PERMISSION_ORG_AND_CHILD.getLevel().equals(dataPermissionType)) { // 如果是table,那么直接加inner,如果不是,然后直接在where条件下加子查询 if (plainSelect.getFromItem() instanceof Table) { Table fromTable = (Table)plainSelect.getFromItem(); ///数据主表 Table dataTable = null; ///// Table innerTable = null; if (fromTable.getName().equalsIgnoreCase(dataTableName)) { dataTable = (Table)plainSelect.getFromItem(); } // 如果是查询,在这里使用inner join关联过滤,不使用子查询,因为join不需要建立临时表,所以速度比子查询快。 List<Join> joins = plainSelect.getJoins(); boolean hasPermissionTable = false; if (CollectionUtils.isNotEmpty(joins)) { Iterator joinsIterator = joins.iterator(); while(joinsIterator.hasNext()) { Join join = (Join)joinsIterator.next(); // 判断join中是否存在t_sys_organization表,若存在,则直接使用,若不存在,则新增 FromItem rightItem = join.getRightItem(); if (rightItem instanceof Table) { Table table = (Table)rightItem; // 判断inner的主表是否存在 if (null == dataTable && table.getName().equalsIgnoreCase(dataTableName)) { dataTable = table; } // 判断inner是否存在需要inner的表 if (table.getName().equalsIgnoreCase(innerTableName)) { hasPermissionTable = true; innerTable = table; } } } } //如果没有找到数据主表,然后直接抛出异常 if (null == dataTable) { throw new BusinessException("数据权限配置的主表未在SQL语句中找到,数据权限过滤失败。"); } ///如果没有这个table,则添加一个新的innerjoinin if (!hasPermissionTable) { innerTable = new Table(innerTableName).withAlias(new Alias(innerTableAlias, false)); Join join = new Join(); join.withRightItem(innerTable); EqualsTo equalsTo = new EqualsTo(); equalsTo.setLeftExpression(new Column(dataTable, DataPermissionConstant.DATA_PERMISSION_ORGANIZATION_ID)); equalsTo.setRightExpression(new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ID)); join.withOnExpression(equalsTo); plainSelect.addJoins(join); } EqualsTo equalsToWhere = new EqualsTo(); equalsToWhere.setLeftExpression(new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ID)); equalsToWhere.setRightExpression(new LongValue(user.getOrganizationId())); Function function = new Function(); function.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET); function.setParameters(new ExpressionList(new LongValue(user.getOrganizationId()) , new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ANCESTORS))); OrExpression orExpression = new OrExpression(equalsToWhere, function); 如果有数据权限配置,//判断是否有数据权限,所以添加数据权限的机构列表 if(CollectionUtils.isNotEmpty(organizationIdList)) { for (String organizationId : organizationIdList) { EqualsTo equalsToPermission = new EqualsTo(); equalsToPermission.setLeftExpression(new Column(innerTable, DataPermissionConstant.DATA_PERMISSION_ID)); equalsToPermission.setRightExpression(new LongValue(organizationId)); orExpression = new OrExpression(orExpression, equalsToPermission); Function functionPermission = new Function(); functionPermission.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET); functionPermission.setParameters(new ExpressionList(new LongValue(organizationId) , new Column(innerTable,DataPermissionConstant.DATA_PERMISSION_ANCESTORS))); orExpression = new OrExpression(orExpression, functionPermission); } } expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(orExpression)) : orExpression; plainSelect.setWhere(expression); } else { InExpression inExpression = new InExpression(); inExpression.setLeftExpression(buildColumn(dataTableAlias, DataPermissionConstant.DATA_PERMISSION_ORGANIZATION_ID)); SubSelect subSelect = new SubSelect(); PlainSelect select = new PlainSelect(); select.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column(DataPermissionConstant.DATA_PERMISSION_ID)))); select.setFromItem(new Table(DataPermissionConstant.DATA_PERMISSION_TABLE_NAME)); EqualsTo equalsTo = new EqualsTo(); equalsTo.setLeftExpression(new Column(DataPermissionConstant.DATA_PERMISSION_ID)); equalsTo.setRightExpression(new LongValue(user.getOrganizationId())); Function function = new Function(); function.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET); function.setParameters(new ExpressionList(new LongValue(user.getOrganizationId()) , new Column(DataPermissionConstant.DATA_PERMISSION_ANCESTORS))); OrExpression orExpression = new OrExpression(equalsTo, function); 如果有数据权限配置,//判断是否有数据权限,所以添加数据权限的机构列表 if(CollectionUtils.isNotEmpty(organizationIdList)) { for (String organizationId : organizationIdList) { EqualsTo equalsToPermission = new EqualsTo(); equalsToPermission.setLeftExpression(new Column(DataPermissionConstant.DATA_PERMISSION_ID)); equalsToPermission.setRightExpression(new LongValue(organizationId)); orExpression = new OrExpression(orExpression, equalsToPermission); Function functionPermission = new Function(); functionPermission.setName(DataPermissionConstant.DATA_PERMISSION_FIND_IN_SET); functionPermission.setParameters(new ExpressionList(new LongValue(organizationId) , new Column(DataPermissionConstant.DATA_PERMISSION_ANCESTORS))); orExpression = new OrExpression(orExpression, functionPermission); } } select.setWhere(orExpression); subSelect.setSelectBody(select); inExpression.setRightExpression(subSelect); expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(inExpression)) : inExpression; plainSelect.setWhere(expression); } } // 只查询用户拥有机构的数据,不包括子机构 else if (DataPermissionTypeEnum.DATA_PERMISSION_ORG.getLevel().equals(dataPermissionType)) { InExpression inExpression = new InExpression(); inExpression.setLeftExpression(buildColumn(dataTableAlias, DataPermissionConstant.DATA_PERMISSION_ORGANIZATION_ID)); ExpressionList expressionList = new ExpressionList(); List<Expression> expressions = new ArrayList<>(); expressions.add(new LongValue(user.getOrganizationId())); if(CollectionUtils.isNotEmpty(organizationIdList)) { for (String organizationId : organizationIdList) { expressions.add(new LongValue(organizationId)); } } expressionList.setExpressions(expressions); inExpression.setRightItemsList(expressionList); expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(inExpression)) : inExpression; plainSelect.setWhere(expression); } // 个人数据只能查询 else if (DataPermissionTypeEnum.DATA_PERMISSION_SELF.getLevel().equals(dataPermissionType)) { EqualsTo equalsTo = new EqualsTo(); equalsTo.setLeftExpression(buildColumn(dataTableAlias, DataPermissionConstant.DATA_PERMISSION_SELF)); equalsTo.setRightExpression(new StringValue(String.valueOf(user.getId()))); expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(equalsTo)) : equalsTo; plainSelect.setWhere(expression); } //当类型是查看所有数据时,不处理// if (DataPermissionTypeEnum.DATA_PERMISSION_ALL.getType().equals(dataPermissionType)) {//// } // 定制过滤语句 else if (DataPermissionTypeEnum.DATA_PERMISSION_CUSTOM.getLevel().equals(dataPermissionType)) { String customExpression = dataPermissionEntity.getCustomExpression(); if (StringUtils.isEmpty(customExpression)) { throw new BusinessException("没有配置自定义表达式表达式"); } try { Expression expressionCustom = CCJSqlParserUtil.parseCondExpression(customExpression); expression = ObjectUtils.isNotEmpty(expression) ? new AndExpression(expression, new Parenthesis(expressionCustom)) : expressionCustom; plainSelect.setWhere(expression); } catch (JSQLParserException e) { throw new BusinessException("自定义表达式配置错误"); } } } /** * 构建Column * * @param dataTableAlias 表别名 * @param columnName 字段名称 * @return 带表别名字段 */ public static Column buildColumn(String dataTableAlias, String columnName) { if (StringUtils.isNotEmpty(dataTableAlias)) { columnName = dataTableAlias + StringPool.DOT + columnName; } return new Column(columnName); } /** * 注释转化为实体类 * @param annotation 注解 * @return 实体类 */ public static DataPermissionEntity annotationToEntity(DataPermission annotation) { DataPermissionEntity dataPermissionEntity = new DataPermissionEntity(); dataPermissionEntity.setDataPermissionType(annotation.dataPermissionType()); dataPermissionEntity.setDataColumnExclude(annotation.dataColumnExclude()); dataPermissionEntity.setDataColumnInclude(annotation.dataColumnInclude()); dataPermissionEntity.setDataTableName(annotation.dataTableName()); dataPermissionEntity.setDataTableAlias(annotation.dataTableAlias()); dataPermissionEntity.setInnerTableName(annotation.innerTableName()); dataPermissionEntity.setInnerTableAlias(annotation.innerTableAlias()); dataPermissionEntity.setCustomExpression(annotation.customExpression()); return dataPermissionEntity; } @Override public Expression getSqlSegment(Expression where, String mappedStatementId) { return null; }
- 当系统启动时,将初始化数据权限配置到Rediss
@Override public void initDataRolePermissions() { List<DataPermissionRoleDTO> dataPermissionRoleList = dataPermissionRoleMapper.queryDataPermissionRoleListAll(); // 判断租户模式是否开启,如果打开,角色权限需要根据租户进行分类存储 if (enable) { Map<Long, List<DataPermissionRoleDTO>> dataPermissionRoleListMap = dataPermissionRoleList.stream().collect(Collectors.groupingBy(DataPermissionRoleDTO::getTenantId)); dataPermissionRoleListMap.forEach((key, value) -> { // auth:tenant:data:permission:0 String redisKey = DataPermissionConstant.TENANT_DATA_PERMISSION_KEY + key; redisTemplate.delete(redisKey); addDataRolePermissions(redisKey, value); }); } else { redisTemplate.delete(DataPermissionConstant.DATA_PERMISSION_KEY); // auth:data:permission addDataRolePermissions(DataPermissionConstant.DATA_PERMISSION_KEY, dataPermissionRoleList); } } private void addDataRolePermissions(String key, List<DataPermissionRoleDTO> dataPermissionRoleList) { Map<String, DataPermissionEntity> dataPermissionMap = new TreeMap<>(); Optional.ofNullable(dataPermissionRoleList).orElse(new ArrayList<>()).forEach(dataPermissionRole -> { String dataRolePermissionCache = new StringBuffer(DataPermissionConstant.DATA_PERMISSION_KEY_MAPPER) .append(dataPermissionRole.getDataMapperFunction()).append(DataPermissionConstant.DATA_PERMISSION_KEY_TYPE) .append(dataPermissionRole.getDataPermissionType()).toString(); DataPermissionEntity dataPermissionEntity = BeanCopierUtils.copyByClass(dataPermissionRole, DataPermissionEntity.class); dataPermissionMap.put(dataRolePermissionCache, dataPermissionEntity); }); redisTemplate.boundHashOps(key).putAll(dataPermissionMap); }
数据权限配置指南:- 数据权限名称:自定义名称,便于搜索和区分
- Mapper全路径: 将Mapper路径配置到具体的方法名称,例如:com.gitegg.service.system.mapper.UserMapper.selectUserList
- 数据权限类型:只能查看本人(实现原则是在查询条件下添加数据表的creator条件) (实现原则是在查询条件中添加数据表的部门条件)只能查看本部门和子部门 (实现原理是在查询条件下添加数据表的部门条件)可以查看所有数据(不处理)的自定义(添加where子条件)
/** * 查询用户列表 * @param page * @param user * @return */ @DataPermission(dataTableName = "t_sys_organization_user", dataTableAlias = "organizationUser", dataPermissionType = "3", innerTableName = "t_sys_organization", innerTableAlias = "orgDataPermission") @DataPermission(dataTableName = "t_sys_organization_user", dataTableAlias = "organizationUser", dataPermissionType = "2", innerTableName = "t_sys_organization", innerTableAlias = "orgDataPermission") @DataPermission(dataTableName = "t_sys_organization_user", dataTableAlias = "organizationUser", dataPermissionType = "1", innerTableName = "t_sys_organization", innerTableAlias = "orgDataPermission") Page<UserInfo> selectUserList(Page<UserInfo> page, @Param("user") QueryUserDTO user);
行级数据权限配置:数据主表:数据操作中使用的主数据表,如SQL语句中的主数据主表别名:主数据表别名,用于与数据权限表进行inner join操作数据权限表:inner join的数据权限表主要用于使用ancestors字段查询所有子组织机构的数据权限表别名:与主数据表一起使用iner join
列级数据权限配置:排除的字段:配置无权查看的字段,排除这些字段保留的字段:配置有权查看的字段,只保留这些字段
备注:- 这种数据权限设计灵活复杂,一些简单的应用场景系统可能根本不需要,只需要配置行级数据权限即可。
- Mybatis-DatapermisioninterceptorPlus插件使用说明 https://gitee.com/baomidou/mybatis-plus/issues/I37I90
- update,insert逻辑说明:inner只支持正常查询,inner查询,不支持子查询,update,insert,直接使用添加子查询实现子查询的数据权限
- 此外,在我们的实际业务开发过程中,我们只能查看我们的数据权限。一般来说,我们不会通过系统进行配置,而是在编写业务代码的过程中 例如,如果您查询个人订单接口,则个人用户ID必须是接口的参与。当接口被要求时,您只需要通过我们自定义的方法获得当前的登录用户,然后将其作为参数进行输入。通过业务代码实现个人数据的这种数据权限将更加方便和安全,工作量不大,易于理解和扩展。