系统权限设计中RBAC模型的使用

什么是RBAC

RBAC,全称是Role-Based-Access-Control,可以译为基于角色的权限控制,是一种应用非常广泛的权限控制模型。

什么是角色(Role)

角色不是一个用户实体,而是代表了一组行为能力或责任的命名实体,以我们常用的QQ群为例,有以下角色:超级管理员、群主、管理员、普通成员、非群成员。

每个角色都有不同的权限:

  • 超级管理员可以管理任何的QQ群,但是一般只能禁言、删除群,不能管理具体的某个群成员,也不能在群里聊天;
  • 群主可以管理自己的QQ群,可以在群里聊天;
  • 管理员可以管理自己的QQ群,可以聊天,但是不能解散群,也不能任命或撤销其他管理员;
  • 普通成员则没有管理权限,只有在群里聊天的权限
  • 非群成员无管理权限,也无法在群里聊天。

所以可以知道,每个角色都对应着一组行为能力或责任。

隐式的基于角色的权限控制

对于QQ群的例子来说。如果我们要做一个删除某个群成员的动作,伪代码可能如下:

if( user.hasRole('Group Owner') || user.hasRole('Group Manager') ){
    // delete the Group Member
} else {
    // Permission Denied
}

那么现在,如果腾讯的权限策略发生了改变,超级管理员也可以删除某个群的群成员来防止某些不当言论的传播,那么代码就要改为

if( user.hasRole('Group Owner') || user.hasRole('Group Manager') || user.hasRole('Super Manager') ){
    // delete the Group Member
} else {
    // Permission Denied
}

上面这种权限的管理方式,就可以称为隐式的基于角色的权限控制。因为Group OwnerGroup ManagerSuper Manager并不能显式的表达出:它们的角色具有删除群成员的权限。没有任何的代码显式的定义了这些角色的权限。我们只能从代码中隐式的推测出:这三个角色拥有删除群成员的权限。所以程序员们使用if/else语句来反映这些假设。

显式的基于角色的权限控制

了解了隐式的基于角色的权限控制,那么我们就可以知道,显式的基于角色的权限控制要有能力直接表达出:当前用户有权限去删除群成员。这样我们的代码可以调整如下:

if( user.isPermitted('GroupMember:delete:478') ){
    // delete the Group Member
} else {
    // Permission Denied
}

这样从代码中可以看到,如果当前用户被允许删除ID为478的群成员,那么就去删除,否则报权限不足的错误。至于isPermitted()中如何去判断权限的,可能依然是回到了哪些角色有哪些权限的问题。但是不同之处在于,应对上面的需求变更问题时,我们只需要更改user的isPermitted的判断规则,而不用去更改散布在代码中各个地方的if语句。可以做到以最小的变更来应对复杂的需求变化。

隐式 vs 显式

隐式和显式在我看来,其内在的权限控制仍然是一样的,都是基于角色在做权限判断。但对外的抽象则是截然不同的:隐式侧重于某个用户是否有某些角色,显式则直接将问题聚焦于某个用户是否有对某个资源的某个操作的权限。这在写代码中给程序员带来的影响是不一样的。截取一段项目中的代码来佐证:

//检查操作的权限
if(
    !familyDB->isUserForFamily(familyId,userId)&&
    !familyDB->isAdminForFamily(familyId,userId)&&
    !familyDB->isOriginatorForFamily(familyId,userId)
){
    Util::printResult(GLOBALS['ERROR_PERMISSION'], "操作权限错误");
    exit;
}

这段代码判断了用户是否对家族有读取权限。因为我们是隐式的基于角色的权限控制,很直观的想法就是:家族成员、家族管理员、家族创始人这三个角色都有对家族的读取权限。所以这里判断了三个权限。事实上,家族创始人的判断是多余的,因为家族创始人肯定属于家族成员。但是真正在写代码的时候,很有可能考虑不到这个问题。

但如果是显式的基于角色的权限控制,这个if语句就是:

//检查操作的权限
if(
    !familyDB->isUserHasReadPermission(familyId,userId))
){
    Util::printResult(GLOBALS['ERROR_PERMISSION'], "操作权限错误");
    exit;
}

程序员写代码时就不会做出多余的判断。可以得出,显式的抽象表达能力是明显更强的。

新的RBAC:Resource-Based-Access-Control

通过对比隐式显式的区别,我们知道显式是直接检查某个用户对某个资源(Resource)是否有某个权限。所以不如直接抛弃角色(Role)的概念,将资源这个概念引入。这样就有了基于资源的权限控制(Resource-Based-Access-Control)