「SpringCloud」微服务数据权限的设计与实现

ponymar
发布于 2022-4-26 20:36
3175浏览
0收藏

作者 AI全栈程序猿
来源 | 今日头条

一套完整的系统权限需要支持功能权限和数据权限,前面介绍了系统通过RBAC的权限模型来实现功能的权限控制,这里我们来介绍,通过扩展Mybatis-Plus的插件DataPermissionInterceptor实现数据权限控制。

简单介绍一下,所谓功能权限,顾名思义是指用户在系统中拥有对哪些功能操作的权限控制,而数据权限是指用户在系统中能够访问哪些数据的权限控制,数据权限又分为行级数据权限和列级数据权限。

数据权限基本概念:

  • 行级数据权限:以表结构为描述对象,一个用户拥有对哪些数据的权限,表示为对数据库某个表整行的数据拥有权限,例如按部门区分,某一行数据属于某个部门,某个用户只对此部门的数据拥有权限,那么该用户拥有此行的数据权限。
  • 列级数据权限:以表结构为描述对象,一个用户可能只对某个表中的部分字段拥有权限,例如表中银行卡、手机号等重要信息只有高级用户能够查询,而一些基本信息,普通用户就可以查询,不同的用户角色拥有的数据权限不一样。

实现方式:

行级数据权限:

对行级数据权限进行细分,以角色为标识的数据权限,分为:

1、只能查看本人数据;

2、只能查看本部门数据;

3、只能查看本部门及子部门数据;

4、可以查看所有部门数据;

以用户为标识的数据权限,分为:

5、同一功能角色权限拥有不同部门的数据权限;

6、不同角色权限拥有不同部门的数据权限。

第1/2/3/4类的实现方式需要在角色列表对角色进行数据权限配置,针对某一接口该角色拥有哪种数据权限。

第5类的实现方式,需要在用户列表进行配置,给用户分配多个不同部门。

第6类的实现方式比较复杂,目前在市面上的大多数解决方案是:

1、在登录时,判断用户是否拥有多个部门,如果存在,那么首先让用户选择其所在的部门,登录后只对选择的部门权限进行操作;

2、针对不同部门创建不同的用户及角色,登录时,选择对应的账号进行登录。

因秉承复杂的系统简单化,尽量用低耦合的方式实现复杂功能的理念,个人更倾向于第二种方式,原因是:

1、系统实现方面减少复杂度,越复杂的判断,越容易出问题,不仅仅在开发过程中,还在于后续系统的扩展和更新过程中。

2、对于工作量方面的取舍,一个人拥有多个部门不同权限的方式属于常用功能,但是并不普遍,也就是说在一家企业中,同一个用户即是业务部门经理,又是财务部门经理的情况并不普遍,更多的是专人专职。这里要和第5类做好区分,比如你是业务部门经理可能会管理多个部门,这种属于权限一致,只是拥有多个部门权限,这属于第5类。再比如一个总经理,可能会看到所有的业务、财务数据这属于第4类。

所以这里不会采取用户登录后选择部门的方式来判断数据权限。

列级数据权限:

列级数据权限的实现主要是针对某个角色能够看到哪些字段,不存在针对某个用户给他特定字段的情况,这种情况单独建立一个角色即可,尽量采用类RBAC的方式来实现,不要使用户直接和数据权限关联。列级数据权限除了要考虑后台取数据的问题,还要考虑到在界面上展示时,如果是一个表格,那么没有权限的列需要根据数据权限来判断是否展示。这里在配置界面就要考虑,在角色配置时,需要分为行级数据权限和列级数据权限进行不同的配置:行级数据权限应该配置需要数据权限控制的接口,数据权限的类型(上面提到的1234);列级数据权限除了需要配置上面提到的之外,还需要配置可以访问的字段或者排除访问的字段。「SpringCloud」微服务数据权限的设计与实现-鸿蒙开发者社区

在资源管理配置资源关联接口的数据权限规则(

t_sys_data_permission_role),通过RBAC的方式用角色和用户关联,在用户管理配置用户同角色的多个部门数据权限,用户直接和部门关联(t_sys_data_permission_user)。系统数据权限管理功能设计如下所示:「SpringCloud」微服务数据权限的设计与实现-鸿蒙开发者社区

数据权限表设计:

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;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
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只能查看本人 2只能查看本部门 3只能查看本部门及子部门 4可以查看所有数据',
  `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;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

数据权限缓存(Redis)设计:

  • Redis Key:

多租户模式:

auth:tenant:data:permission:0(租户):mapper_Mapper全路径_type_数据权限类型

普通模式:

auth:data:permission:mapper_Mapper全路径_type_数据权限类型

  • Redis Value:存放角色分配的DataPermissionEntity配置

数据权限插件在组装SQL时,首先通过前缀匹配查询mapper的statementId是否在缓存中,如果存在,那么取出当前用户的数据权限类型,组装好带有数据权限类型的DataPermission缓存Key,从缓存中取出数据权限配置。

在设计角色时,除了需要给角色设置功能权限之外,还要设置数据权限类型,角色的数据权限类型只能单选(1只能查看本人 2只能查看本部门 3只能查看本部门及子部门 4可以查看所有数据5自定义)

代码实现:

因DataPermissionInterceptor默认不支持修改selectItems,导致无法做到列级别的数据权限,所以这里自定义扩展DataPermissionInterceptor,使其支持列级权限扩展

@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);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

自定义实现DataPermissionHandler数据权限控制

@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;
                //inner数据权限表
                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的表是否存在
                            if (table.getName().equalsIgnoreCase(innerTableName))
                            {
                                hasPermissionTable = true;
                                innerTable = table;
                            }
                        }
                    }
                }
                //如果没有找到数据主表,那么直接抛出异常
                if (null == dataTable)
                {
                    throw new BusinessException("在SQL语句中没有找到数据权限配置的主表,数据权限过滤失败。");
                }
                //如果不存在这个table,那么新增一个innerjoin
                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;
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.

系统启动时初始化数据权限配置到Redis

  @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);
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

数据权限配置指南:「SpringCloud」微服务数据权限的设计与实现-鸿蒙开发者社区

  • 数据权限名称:自定义一个名称,方便查找和区分
  • 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);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

行级数据权限配置:

  • 数据主表:主数据表,用于数据操作时的主表,例如SQL语句时的主表
  • 数据主表别名:主数据表的别名,用于和数据权限表进行inner join操作
  • 数据权限表:用于inner join的数据权限表,主要用于使用ancestors字段查询所有子组织机构
  • 数据权限表别名:用于和主数据表进行inner join
  • 列级数据权限配置:
  • 排除的字段:配置没有权限查看的字段,需要排除这些字段
  • 保留的字段:配置有权限查看的字段,只保留这些字段

备注:

  • 此数据权限设计较灵活,也较复杂,有些简单应用场景的系统可能根本用不到,只需配置行级数据权限即可。
  • update,insert逻辑说明:inner时只支持正常查询,及inner查询,不支持子查询,update,insert,子查询等直接使用添加子查询的方式实现数据权限
  • 还有在这里说明一下,在我们实际业务开发过程中,只能查看本人数据的数据权限,一般不会通过系统来配置,而是在业务代码编写过程中就 会实现,比如查询个人订单接口,那么个人用户id肯定是接口的入参,在接口被请求的时候,只需要通过我们自定义的方法获取到当前登录用户,然后作为参数传入即可。这种对于个人数据的数据权限,通过业务代码来实现会更加方便和安全,且没有太多的工作量,方便理解也容易扩展。

分类
收藏
回复
举报
1
1条回复
按时间正序
/
按时间倒序
wx6614ec8436161
wx6614ec8436161

内容不完整

回复
2024-10-15 09:46:26


回复
    相关推荐