多租户系统设计之权限控制

概述

业务层面的隔离是用户可以直接感知的隔离,也是多租户系统必须实现的隔离,在上篇文章中提到的数据隔离主要是针对数据存储层面而言的,用户一般感知不到,所以如“基于数据行的租户唯一标识”方案中,即使存储在相同的数据表也是可以的。在系统设计层面,业务隔离就是需要做好权限控制。

基于RBAC模式的权限模型设计

多租户系统的权限控制也是基于RBAC模式来设计的,即用户,角色,权限和资源(针对简单业务可以将角色和权限合并为权限即可,以下介绍的业务中台项目就是基于这种方式做的)。
但是针对资源,或者针对服务端而言,是数据资源,需要限定在租户的数据空间内,如同样是财务出纳角色,拥有打款的权限,但是不同租户只能针对自己的账单进行打款操作。这个主要是通过为资源绑定租户标识来实现:
33.png

数据资源的租户绑定

数据资源的租户绑定需要结合以上介绍的数据存储隔离方案来考虑。
对于分库方案中的方式一,由于是独立存储、独立部署、独立域名,故每个租户都是访问自己独立的系统,在系统层面就形成了数据资源的租户绑定,故在应用层面和数据存储层面无需对数据资源进行特殊绑定。
而对于其他数据隔离方案,不管是基于分库分表实现的数据路由,还是共享表内的租户标识列,都需要在应用层通过租户标识来区分出不同的租户的数据,然后在进行进一步操作,说白了就是每个数据资源都需要拥有一个租户标识符来确定自己所属的租户。
对于分库或者分表方案而言,由于租户拥有独立的分库或者分表,所以在具体存储层面则可以不需要为每行数据都带上这个租户标识,不过具体需要结合所使用的分库分表中间件来确定是否需要;而对于基于数据行的租户唯一标识的共享库,共享表方案而言,则是必需的。

案例分析

在业务中台项目中,我们设计了两大核心角色,分别是系统管理员和业务线运营管理员(相当于租户管理员),其中系统管理员是拥有对所有业务线的权限,而每个业务线运营管理员只拥有自己业务线的管理权限。

在应用代码层面,需要通过业务线标识符(租户标识)来区分当前登陆用户是否可以操作对应租户的数据,基本流程为:

  1. 根据当前登陆用户的角色来确定是否有该操作的权限(简单场景,角色与权限可以合并为权限),如进行指定业务线下的操作时,是否是业务线运营管理员的角色;
  2. 如果角色校验通过,则需要进一步确定是否对所操作的数据资源具有权限,这个主要是通过在用户的账号记录中带有租户标识符,然后与当前需要操作的数据资源的租户标识符来匹配,如果匹配成功,则校验通过。
核心实现

以下为简化版的代码实现:

  1. 核心入口:指定用户,是否拥有指定租户的操作权限的方法:用户权限上下文集合,需要操作的租户标识,需要执行的操作

     /**
      * 指定用户,是否拥有指定业务的操作权限
      * @param memberInfo 用户信息
      * @param targetBizCode 目标操作的租户标识
      * @param targetOperate 目标操作的操作类型
      * @return
      */
     public boolean hasPermission(MemberInfo memberInfo, String targetBizCode, String targetOperate) {
    
    	// 遍历当前用户的权限集合,判断是否具有对指定租户标识targetBizCode,进行指定操作targetOperate的权限
    	return memberInfo.getPermissions().stream()
    		.anyMatch(p -> hasAllowedPermission(p, targetBizCode, targetOperate));
     }
    
  2. 是否拥有操作权限:针对用户权限上下文集合的每个权限进行检查是否具有操作权限

     /**
      * 是否拥有操作权限
      *
      * @param pemission 用户的权限上下文
      * @param targetBizCode 需要访问的租户标识
      * @param targetOperate 需要执行的操作
      * @return
      */
     private boolean hasAllowedPermission(PermissionDO pemission, String targetBizCode, String 
     targetOperate) {
    
    	// 权限定义:包含一个操作集合
    	PermissionSpec permissionSpec = permissionSpecs.get(pemission);
    
    	// 检查是否具有对指定租户进行指定操作的权限:
    	// 1. 操作集合中的其中一个操作与目标操作targetOperate匹配
    	// 2. 需要访问的租户标识targetBizCode,与用户允许访问的租户标识pemission.getBizCode()匹配
    	return permissionSpec.operateSpecs.stream().anyMatch(operateSpec ->
    		canOperate(pemission.getBizCode(), targetBizCode, targetOperate, operateSpec)
    	);
     }
    
  3. 是否可以执行指定操作:这里强调的“执行”指定操作,除了检查操作是否匹配之外,更重要的是检查租户标识是否匹配。

     /**
      * 是否可以执行指定操作
      * @param allowedBizCode 用户可以访问的租户标识
      * @param targetBizCode 需要判断的目标租户标识
      * @param targetOperate 需要判断的目标操作类型
      * @param operateSpec 权限支持的操作定义
      * @return
      */
     private boolean canOperate(String allowedBizCode, String targetBizCode, String targetOperate, 
     OperateSpec operateSpec) {
    
    	// 判断这个操作是否是需要执行的目标操作
    	boolean operateMatch = operateSpec.getName().equalse(targetOperate);
    
    	// 核心点:进一步校验租户标识
    	if (operateMatch) {
    		return targetBizCode.equals(allowedBizCode);
    	}
    
    	return false;
     }
    

总结

针对以上代码来说,两个点,第一对于简单权限模型,可以将角色与权限统一为权限,如以上的Permission集合,第二对于多租户系统,除了检查是否具有操作权限之外,还需要结合租户标识进一步检查是否可以执行指定操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值