设计实战——基于RBAC的功能权限设计思考

关于功能权限的控制,使用RBAC可以很好地解决,网上有大量的理论性介绍,但落地过程中,仍然有一些具体的问题,需要结合自己平台或系统的规模、面向场景选择最合适的方案,从而形成最优的设计。下面我结合自己研发平台的经验,具体说一说。

理论基础

基本概念

权限控制是一个系统的核心功能,可以分为两类,一类是功能权限,一类是数据权限。今天我们说的是功能权限。

功能权限,是指系统用户能进行哪些操作,通常是菜单和按钮权限,如打开订单菜单,查询订单列表,创建新订单。对于功能权限,有标准化的解决方案,也即RBAC,通过权限项、角色、用户三张主表,以及角色-权限项对应关系表和角色-用户对应关系表两张辅助表,一共5张库表即可实现功能权限的功能,系统管理员通过系统界面即可实现灵活的权限分配和维护。

RBAC模型

实际上RBAC只是一个统称,通常说的,都是指RBAC0,也就是上面的5张表的方案,其实还有进阶版RBAC1、RBAC2、RBAC3。

RBAC1建立在RBAC0基础之上,在角色中引入了继承的概念。比如大区经理和片区经理的关系。

RBAC2模型主要增加了责任分离关系,面向授权访问添加了诸多约束,这也是为了满足业务的需要。比如在企业内部,出纳和会计是两个不同的角色,这两个角色如果由一个人来担任,则可能会出现资金流失而无人知晓的情况,所以在RBAC模型实现时,通过授权约束,限制同一个人被授予出纳和会计这两个角色,以规避风险;

RBAC3模型是RBAC1和RBAC2的组合,既添加了角色继承,又有访问控制约束,以满足更加复杂的业务需求。

实战思考

角色

单层平铺还是树形结构?

在小型应用中,角色权限数量有限的情况下,单层平铺简单实用,但在大中型应用中,角色数量成百上千个,如果依旧平铺则难以管理和使用。采用树形结构后,可以方便地添加虚拟节点(如角色分类)。

角色还是用户组?

角色的称呼偏技术,对于业务用户而言,称为用户组更合适,即一组用户的集合;

业务意义上的岗位和职务,与传统意义的角色含义一致,如部门经理、固定资产接口人。

更改为树形结构后,可以进一步扩展其功能,例如,新建通讯(通知)组,将一部分人放到其下,可以供消息模块(短信、邮件、电话)等使用。

涉及到工作流集成,同样存在一部分流程中的环节处理人员,往往也是某些特定用户集合,并非出于功能权限分配目的,放到用户组角色里更合适。

换个角度想,传统意义上用于功能权限控制的角色,更适合作为用户组的一个分支。

是否权限继承?

构建成树形结构后,面临一个新的问题,即是否要考虑权限继承问题,例如,给某一角色(用户组)分配的某一权限,则其上级角色和权限自动也自动拥有了其权限,也就是通常意义上的员工有的权限,领导也自动有了,这实际是RBAC1模型要解决的问题。
这么做看上去会减轻一定的权限初始化工作以及权限维护工作量,但实际上结果可能是相反的,增加权限维护的复杂性,并且某些业务功能并不适配这种模式。例如,某个底层功能,如班组作业,需要获取当前班组编码或数据,这个功能菜单,让部门经理打开会无法正常显示或使用。
上面这个例子,实际深入思考下,并不是权限继承机制的问题,而是使用这种机制,需要从全局好好规划角色,明确哪些角色是继承关系。班组成员和部门经理是上下级关系,但是从角色继承角度而言,并不应该存在继承关系。存在继承关系的角色,应该是部门管理员-》公司管理员-》集团管理员。

引入权限继承,会增加系统设计、实现、运维的复杂性,因此,不考虑权限继承问题,树形结构仅用于组织角色(用户组)。

用户组规划

按照上述用户组的分类考虑,规划如下:
1.角色:传统意义上的岗位和职务,如部门经理、固定资产接口人,其下可以进一步分公共角色和专用角色,公共角色全局公用,如部门经理、固定资产接口人,每个部门都有该角色;专用角色往往是特定部门特定岗位才有的,如采样人员、巡检人员,可以在用户组下建组织机构虚拟节点,将这些专用角色挂靠在下面,更方便管理(使用角色时,不会显示在公共角色下,从而避免展示的总数量过多而影响选择)。
2.通讯组:预置的消息通知用户集合,可供消息模块(短信、邮件、电话)等使用,在某些业务事件发生时,如发生火灾,系统自动发送应急短信给通讯组用户。
3.流程岗位:工作流流程审批专用,如合同审查人员,会签人员,往往是为某条或某几条流程设置的人员集合,与管理上的岗位和职务没有很强的关联性,也不适合在常规功能权限分配是显示出来。
……

权限控制实现思路

权限控制又很多现成的组件可用,如Apache Shiro,Spring Security,如果系统自身比较小,自己基于五张表实现一个也不难,这里重点说下,后端收到前端请求,如何来判断当前用户是否有权限这个事。这个地方走过弯路,开始想基于路径,后来发现问题较多,实用性差,变更为基于编码的方式,下面具体说说。

基于路径的自动匹配

权限项最初的方案是基于rest理念,在权限项中配置路径,当收到前端请求时,由SpringMVC框架映射到具体某个方法上,在这个方法执行前验证请求路径是否与权限项中配置的路径匹配,但这种方式存在一个问题,即通配符引发的匹配问题。

例如/system/user/* 的get请求,通常是获取某个具体用户的信息,但这种路径,会与一些其他方法冲突,例如分页方法的路径为/system/user/page,这两个请求从前端发起,SpringMvc框架能区分开应该映射到哪个方法,但是在进行权限验证时,这两个路径实际都能映射到/system/user/*上,也就是说,如分配了查看用户权限,也相当于给分页方法分配了权限。

此外,实际并不希望权限的颗粒度那么细,具体到每一个方法,否则会给权限维护带来麻烦。实际往往希望将一系列相关的操作打包成1个权限项,例如一旦给用户分配了用户查询权限,需要为用户的获取列表list和分页查询都自动设置上权限。

基于编码的预置控制

如上,为解决基于路径方法权限判断的问题,以及权限项打包授权问题,采用了权限编码的方式,一方面,在权限项维护时添加权限编码,然后在后端方法上添加注解,明确该方法受哪个权限编码控制,在前端按钮上也通过该权限编码来控制可见性。

这种方式解决上基于路径的两个问题,但在权限编码变更时,需要开发人员同步调整前端和后端。权限编码一般情况下也不会变,但若出现业务需求,调整菜单路径,则会引发该问题,若要解决该问题,除非将依据调整为权限项的标识,即无任何业务含义,这种方案虽然可以任意调整菜单层级,但可读性极差,在开发和维护阶段很容易出错,整体评估下来,还不如采取有业务含义的编码更清晰和易于维护。上面提到的调整菜单问题,开发人员进行同步调整即可,工作量较小,不是什么大问题。

若有收获,就点个赞吧

猜你喜欢

转载自blog.csdn.net/seawaving/article/details/128587561