一、权限(Permission)
在与人沟通的过程中,我们很多次提到了权限,但是权限具体的含义每个人理解的含义都不明确,这样很容易造成双方信息不对称,有的人就只是把权限理解成某个页面的是否可访问,但是有的人却理解成其他的东西。
所以我们要彻底的定义一下权限是什么?
“权限”这个词语,我们谈论时到底是名词属性还是动词属性?这对于权限的含义很重要。
如果是名词属性的话,那么它应该是有具体的指代物;如果是动词,则应该具有行为表示。
- 权限的名词属性:api接口、页面、业务功能等。
- 权限的动词属性:可访问、新增、编辑、可操作、不可操作等。
那么我们现在来看,其实权限是名词、动词属性的综合,它表达了两层含义,即控制的对象及对象的操作。
(一)控制对象(Object)
我们在授权时总是在说:要对哪些事物可以做什么的权限?
控制对象(Object)是系统所要保护的资源(Resource),可以被访问的对象,又称为权限对象、受控对象。
资源的定义需要注意以下两个问题:资源具有层次关系和包含关系。
例如,网页页面是资源,网页页面上的按钮、文本框等对象也是资源。一级菜单的优先级高于二级菜单,菜单的优先级高于按钮,如可以访问按钮,则必须能够访问页面。
这些资源集中在一起,对每个资源设立自己的父编号,从而在进行优先级的选择和显示的时候,能按照正确的方式进行。
1.资源的类别与实例
这里提及的资源概念是指资源的类别(Resource Class),不是某个特定资源的实例(Resource Instance)。
资源的类别和资源的实例的区分以及资源的粒度的细分,有利于确定权限管理系统和应用系统之间的管理边界,权限管理系统需要对于资源的类别进行权限管理,而应用系统需要对特定资源的实例进行权限管理。
两者的区分主要是基于以下两点考虑:一方面,资源实例的权限常具有资源的相关性。即根据资源实例和访问资源的主体之间的关联关系,才可能进行资源的实例权限判断。
例如,在销售管理系统中,需要按照营业区域划分不同部门的客户,销售A和销售B都具有修改客户资料这一受控的资源,这里“客户档案资料”是属于资源的类别的范畴。如果规定销售A只能修改销售A管理的客户资料,就必须要区分出资料的归属,这里的资源是属于资源实例的范畴。
客户档案(资源)本身应该有其使用者的信息(客户资料可能就含有营业区域这一属性),才能区分特定资源的实例操作,可以修改属于自己管辖的信息内容。
另一方面,资源的实例权限常具有相当大的业务逻辑相关性。对不同的业务逻辑,常常意味着完全不同的权限判定原则和策略。
(二)权限项(Ops)
抽象地来说,权限是对受保护的资源操作的访问许可(Access Permission),是绑定在特定的资源实例上的。
对应地,访问策略(Access Strategy)和资源类别相关,不同的资源类别可能采用不同的访问模式(Access Mode)。
例如,页面具有能打开、不能打开的访问模式,按钮具有可用、不可用的访问模式,文本编辑框具有可编辑、不可编辑的访问模式。同一资源的访问策略可能存在排斥和包含关系。
控制对象和访问策略的组合才能称为一项权限,因此,权限考虑各中资源,还要考虑各种操作,各操作项有时又称为权限项。
权限项的不同组合,也成就了角色的复杂多变,用户权限的千变万化。
1.各种操作
对待操作,大的范围包括:无、只读、编辑;而编辑又可以分为:增加,删除,更新等等,这些基本操作还可以进行不同情况的组合来满足多方面的需求。最终通过资源object和操作operate建立关系,形成权限(Permission)。
然而诸多的变化都来自这些最基本元素的的基本操作,即增加、修改、删除、查看。
对于实际应用而言,增加、修改的动作就是对资源数据进行“写”操作,而查看动作便是对资源数据进行“读”。
“读”、“写”、“删除”三者共同构成了对资源数据的全部操作。
(1)查看操作
“查看”操作是最为复杂的操作,也是四个操作里面最为核心的操作。“查看”动作就是调取符合条件的信息的资源数据。
“更改”和“删除”动作的第一步便是需要“查看”动作调取资源数据来作为它们的操作对象。
在“增加”资源数据时,一些系统基础数据也需要“查看”动作将数据调取出,方便和规范“增加”数据的操作。
故而“查看”动作便是其他动作在权限实现过程中的第一道关口。因此,“查看”动作关注如下几个方面内容:
- 第一,用户是否为该条资源信息的产生用户。
- 第二,用户的部门与所需查看信息的部门是否相匹配
- 第三,用户是否为所查看信息的特别专属用户。
以上便是对用户权限最基本单元权限项的简单分析,通过对基本操作对基础资源的划分来实现静态职责分离的约束。
通常,如果用户只拥有“读”资源数据的权限,而没有“写”和“删除”的权限,这种情况便是属于静态职责分离的约束。
同样,如果用户既拥有了“读”资源数据的权限,又拥有了“写”资源数据的权限,却不能“删除”,这时用户便是同时拥有“读写”权限,这便是没有进行静态职责分离的约束。而往往实际的工作中,面临的情况却是更为复杂。
(2)新增操作
就“增加”资源数据这个动作而言,便存在几种情况:
- 增加一条数据,此数据必须是完整的结构才能被增加。
- 增加一条不完整的数据,此数据可以不完成,同一用户可以完成“增加”的动作之后一段时间内进行继续完善信息的动作。
- 增加一条不完整的数据,此数据其后的内容,必须由另一用户在一定的时间段内完善。
(3)修改操作
“修改”的动作更为复杂,现可简单列举几个方面内容。
“修改”数据关注点主要涉及到以下几个方面:
第一,修改该条数据的时间要求。
第二,此条数据的修改是属于改正输入错误类型还是属于数据状态正常改变类型。“修改”的动作在业务流程可以代表很多内涵。
比如,在机车的整备过程中,可以是“机统-6 完成查验”、“合格证发放”、“附挂(回送)”及“机车出库”等诸多操作。
(4)删除操作
“删除”动作通常指的是标记删除。区别于物理删除,即不是真正意义上的删除,而是通过数据库中数据字段的特殊标记,使得这些被“删除”数据无法显示出来而已。
删除的动作其实就是特殊的“修改”操作。“删除”操作有两大主要内涵:
第一,对于错误数据的清除;
第二,在业务流程的流转环节中,对于数据的提交。数据进入下个流程中后,上一个流程便“删除”相应的数据。
这样使得系统的整个流程化更加清晰。
2.操作权限
操作权限就是将操作视为资源,比如删除操作,有些人可以,有些人不行。
对于服务端来说,操作就是一个接口。对于前端来说,操作往往是一个按钮,所以操作权限也被称为按钮权限,是一种细颗粒权限。
在页面上比较直观的体现就是没有这个删除权限的人就不会显示该按钮,或者该按钮被禁用:
前端实现按钮权限还是和之前导航菜单渲染一样的,拿当前用户的权限资源id和权限资源字典对比,有权限就渲染出来,无权限就不渲染。
操作权限其实是比页面权限安全的,为什么呢?
这个安全主要体现在服务端上,页面渲染不走服务端,由前端页面自己控制。但点击按钮的事件必须访问接口,必须得走服务端,我们只需要对每个接口进行权限判断就OK了!
二、功能权限和数据权限
软件系统的权限包含两部分:
- 功能权限,指各个角色可以操作的界面、按钮等,例如管理员可以进行新增、删除、修改等操作;运营人员在同样的页面上只能使用各种筛选条件查看数据,无法做更改。
- 数据权限,指各个角色在各页面中能看到的数据范围,例如分公司管理员在订单查询页能看到分公司的所有订单,而区域主管在订单查询页只能看到所在区域的订单。
(一)功能权限
功能权限是指用户能否操作某些功能的权限,简单来说就是用户能否点击系统中的某个按钮或链接(打开页面也是点击链接的一种)。
根据权限不同层次的特点,功能权限的建立可以分为三个层次:数据层权限,业务层权限、表示层权限。
根据权限的三层结构分析,其内涵主要体现在对系统菜单的访问以及页面中的功能按钮控制,文件的修改控制以及页面上数据的可见性等。
在数据库的具体设计上,菜单、文件、页面元素可以归为一类,功能操作可以归为一类。
权限:对受保护的资源操作的访问许可(Access Permission),是绑定在特定的资源实例上的。
对应地,访问策略(Access Strategy)和资源类别相关,不同的资源类别可能采用不同的访问模式(Access Mode)。
在定义操作权限时,需要与查看权限相关联。
- 先有查看权限,才有操作权限。
- 取消了查看权限,就自动取消了操作权限。
- 当用户没有操作权限时,对应的操作按钮或链接最好隐藏,不要等到用户点击后才告诉他没有权限,即“可见即可用”。
1.检验权限的场景
触发验权有很多种情况,最常见的是以下五种:
- 获取当前用户有权的应用范围;
- 获取当前用户有权的菜单范围;
- 判断当前用户执行当前操作是否有权;
- 获取当前用户在当前应用当前表单的查询权下有权的隔离维度范围;
- 获取当前用户在当前应用当前表单下有权和无权的权限项的范围。
获取有权的应用范围
如下图,用户在打开选择“应用”时,会对用户拥有的应用验权。
判断用户对某个应用有权的逻辑
-
要求用户拥有该应用下至少一个授权对象的其中一个权限项。(针对非全员应用);
-
该应用是“全员应用”,只要用户分配了对应许可分组也会被系统认为有权;
获取有权的菜单范围
当用户进入应用工作台时(例如:应付),加载应用菜单(如下图的左侧菜单)时对用户拥有的应用验权。
菜单验权的逻辑(有权则显示),如下图:
上图中的红圈序号和下方序号是一一对应,“显示”即判定有权:
- 当前菜单所在的应用是全员应用吗?如果是,此菜单有权。
- 当前菜单绑定的表单是否启用“控制功能权限”,并且至少绑定了一个权限项?如果不是,意味着该表单不需要控权,那么此菜单有权。
- 当前菜单的“权限项”配置项为空?如果为空,走步骤4;非空,走步骤5。
- 当前菜单的“打开页面”如果是“单据”或“基础资料”,则验“新增”权,其他情况都验“查询”权。
- 当前表单的权限项是否存在菜单指定验权的“权限项”?(假设菜单的“权限项”指定“提交”, 当前表单权限项添加“查询”、“新增”和“修改”,没有“提交”;则系统认为该表单不希望验这个“提交”权,所以判定菜单有权。
- 根据上游步骤要求验证的权限项,验权当前用户是否具有“用户+应用+表单+权限项”的授权(此处不考虑权限控制类型)。
例如,页面具有能打开、不能打开的访问模式,按钮具有可用、不可用的访问模式,文本编辑框具有可编辑、不可编辑的访问模式。
判断当前执行操作的权限状态
判断用户对某个操作有权的逻辑
- 点击工具栏,验证该操作对应的权限项;
- 授权对象的主业务组织在当前用户授权组织范围;
1)授权对象列表常用条件的业务组织或详情界面所选的组织,显示用户的授权组织;
2)授权组织必须具备授权对象要求的组织职能类型; - 超链接打开先验修改权,有权则打开编辑界面;无修改权则再验查看权,有权则打开查看界面;
举例如下:
给用户直接授权,授权组织:A分公司,功能权限:应付-财务应付单-查询,如下图:
财务应付单列表点击【提交】按钮,系统会验当前用户的“应付-财务应付单-提交”权,并且同时验权限控制类型的范围是否匹配。
不具有权限则提示无权,如下图:
获取当前应用的表单的查询权下有权的权限控制类型范围
列表打开时,结算组织在“常用条件”中的组织范围,会触发查询有权组织范围的处理,以获取当前用户在“应付-财务应付单-查询”下分配的组织范围,如下图:
如果授权时有选择“分配组织及下级”(如下图),上述结算组织会根据该表单主业务组织的指定组织职能的视图方案来计算下级范围。
获取当前应用当前表单下全部有权和无权的权限项的范围
对于无权按钮是隐藏,还是置灰,这个根据需求决定,一般“无权按钮”隐藏比较多。
例如:“应付-财务应付单” 只绑定了“查询、修改、新增、审核和反审核”,用户U被管理员授权:
1、“应付-财务应付单-查询-A组织”;
2、“应付-财务应付单-修改-B组织”;
3、“应付-财务应付单-反审核-C组织”。
设计页面的功能时,要考虑页面中的各个功能点是否要做权限控制。相应地,在每个页面的设计文档中,除了描述页面功能本身,还需要描述系统中不同角色对页面中的各个功能点的访问权限。我们通常用权限表来描述权限、角色的配置关系,这张表在产品设计阶段就需要准备好。
(二)数据权限
数据权限就是系统中存在的数据、资源能够被谁查看和管理。
角色在页面中能查到的数据范围,叫该角色的数据权限。所谓能查到的数据范围,不是指能看到的数据字段,而是指能查出来的数据集合。
数据权限是指用户能否查看某些数据的权限,所以也称查看权限。
系统里每个数据都要定义可查看的角色,大到一个菜单栏目,小到一个具体字段,都属于系统中的数据。在定义角色的查看权限时,粒度要视业务需求而定,不过需要与产品的结构对应,不能出现数据位置模糊、权限相互矛盾的情况。
例如,针对订单列表页,数据范围可能是某个城市的所有订单,也有可能是某个账户下的所有订单,也可能是某几个账户下的所有订单。
又例如,在说明“人事总监可以看到公司所有人的工资单,其他角色只能看到自己的工资单”这个权限时,就应该从一级菜单开始,按产品的结构,一层一层地指定数据路径,即XX角色具有XX菜单-XX栏目-XX数据的查看权限。
针对数据权限的控制,常见的实现方案如下:
-
方案一,通过组织机构树控制。该方案根据账号所在组织机构树中的节点位置,来判断能够查询的数据范围。这种方式最复杂,但最灵活,能够支持各种复杂的业务数据权限诉求。·
-
方案二,通过客户地区控制。该方案根据账号所在区域来判断允许查看的数据范围。这种方式简单、容易实现,但灵活性差,只能满足非常初级的数据权限管理诉求。
数据权限是角色权限的重要属性,由于数据范围太灵活,如果在角色加入“数据权限”tab页,如果账号下的数据量较大,用户编辑数据权限的操作会很繁琐。因此,因此现在主流的后台设计都会使用“组织结构”来对应数据权限。
1.基于组织机构树的数据权限控制
以M公司分销平台的业务场景为例,介绍如何通过组织机构树进行数据权限控制。
背景介绍:
M公司是XX集团下属的电商公司,成立五年,主营生鲜商品,以C端客户为主,业务稳定。M公司在三个月前尝试开展分销业务(也可以叫大客户业务,或者叫To B业务),成立销售团队,将生鲜品卖给企业客户。分销业务的目标客户是大型的餐饮连锁集团,以及大型生鲜分销商等企业级客户。
需要注意的是,M公司并不会参与客户对商品的二次转卖环节(例如不会参与下一级批发商购买生鲜品后的分销过程)。业务试点在北京、上海开展,三个月以来业务发展迅速。
由于分销业务发展迅速,现急需配套的软件系统来提升业务效率,控制经营风险。公司希望寻找新的业务增长方向,考虑到在生鲜领域的数字化实践水平较高,希望这次搭建的分销系统,除了可以给自己使用,未来还可以作为SaaS化软件产品对外售卖,发展成公司的新业务。
分销业务场景具备足够的复杂性,既要支持公司对客户的运营管理,又要支持客户的自主管理,设计的系统具备比较全面的功能。再次,分销业务模式涉及复杂的多层级子母账号管理和组织机构管理,这是B端产品设计中的典型问题,也是设计的难点。
M公司分销平台的功能模块设计
分销业务的三个独立系统(分销商城前台、分销客户管理后台、分销运营管理后台)。
- 分销商城前台即客户下单的H5工具,是一个经典的电商C端系统,分销客户需要在上面完成下单购买操作,也需要完成自我管理(例如对下属门店的管理,对发票、售后的管理),因此该系统主要包括购买流程和个人中心两大部分。
- 分销客户管理后台是给分销客户管理员使用的系统,主要用来管理下属门店和子账号;还需要随时了解下属门店和子账号的经营情况,因而需要查询所有下属门店和子账号的数据;此外还需要进行统一的财务管理。
- 分销运营管理后台是支持M公司分销业务的核心业务系统,同时也是一套典型的电商管理后台。典型的电商管理后台需要具备商品定价管理、财务管理、风控管理、运营管理、客户管理、报表管理几大模块,另外,针对案例中的分销业务,还需要具备账期管理模块。
客户诉求
在目前的分销客户中,有比较大型的集团客户,下设若干机构、库房和门店。调研时,集团客户有如下诉求:
- 上海分公司采用了中央仓库模式,客户从M公司采购商品后,商品首先会被配送到中央仓库,再由客户自己从中央仓库向上海地区各门店发货。因此上海分公司需要开通采购员账号,以实现在中央仓库系统中下单。
- 广州天河区也采用了中央仓库模式,客户从M公司采购商品后,商品首先会被配送到天河区中央仓库,再由客户自己从中央仓库向天河区各门店发货。因此天河区需要开通采购员账号,以实现在天河中央仓库系统中下单。
- 广州其他区是门店自采模式,即门店采购员自行下单采购,商品直接从M公司配送到门店,因此需要针对每个门店创建采购员账号。
- 广东省还需要一个高级别的采购员账号,能够帮广东各仓库和门店代下单。
门店对象,图中用浅灰色节点表示,是下单的目标。案例中提到中央仓库、库房和门店,这三个看上去不是一回事,但本质上都是一个收货地址固定的收货对象,所以没必要设计成三个,统一抽象成一个实体——门店即可。
账号对象,图中用白色节点表示,代表系统的用户(例如采购员、运营人员等)。通过将账号和门店对象关联在不同的树形结构的节点上,再结合不同数据权限范围的设置,就可以实现通过不同的账号管理不同门店的下单操作。
每个机构节点都有一个“上级机构”的关键字段(即父节点ID),基于这个字段,可以绘制出完整的组织机构树;每个账号或门店对象只能隶属于一个机构节点。
案例详解
假设我们实现了完整的组织机构树和客户账号模型,如果系统能够实现通过读取这棵树上的节点来实现数据权限的控制,那么数据权限的管理将变得十分灵活。
为了向大家解释如何通过组织机构树来实现灵活的数据权限管理,我们创建一个新的“客户-采购员”角色来对比说明。
我们把新创建的角色称为“客户-采购员2”,原来的角色称为“客户-采购员1”。
注意:现在关于客户的采购员有两种角色,“客户-采购员1”能够查询用户当前所在节点及其所有子节点上的数据,“客户-采购员2”只能查询用户当前所在节点上的数据。
分销业务的组织机构树如图所示,我们对图中的账号和门店编了号,账号被赋予的角色在相应的虚线框中进行了标注,而且“分销总部”下面新增了一个账号。
根据这些信息,我们足够判断每个角色所能查询的数据范围。
- “账号0”被赋予“分销-管理员”角色,是“分销总部”根节点的下属节点,该账号处于整个分销业务顶层根节点的位置,且数据权限范围是“当前节点及其子节点”,因此,在账号管理、门店管理、订单管理等功能中,“账号0”可以检索出分销业务下的所有账号、门店、订单的信息,并对其进行增删改查操作。
- “账号1”被赋予“分销-运营人员”角色,和“账号0”的数据权限范围相同,但是页面上的功能权限略有不同。
- “客户1”是某个具体的分销业务客户,“账号2”是“客户1”的根账号,被赋予“客户-管理员”角色,且数据权限范围是“当前节点及其子节点”,这说明“账号2”可以查询并管理“客户1”下面的所有账号、门店数据,并可以查询其中的所有订单数据。
- “账号3”属于“上海机构”的子节点,被赋予“客户-采购员1”的角色,数据权限范围是“当前节点及其子节点”,因此“账号3”可以查询“上海机构”节点下的所有订单数据。
- “账号5”属于“广州”节点的子节点,被赋予“客户-采购员2”的角色,该角色和其他角色不同,数据权限范围是“当前节点”,因此“账号5”在订单页面只能查看同级别的门店订单,即“门店2”和“门店3”的订单,但是看不到所在节点子节点下的门店订单,即看不到“天河”节点下的“门店4”“门店5”的订单。
- “账号6”属于“天河”节点的子节点,被赋予“客户-采购员1”的角色,数据权限范围是“当前节点及其子节点”,因此“账号6”可以查询“天河”节点下所有门店的订单数据。我们将以上所有账号能查询的门店的订单数据范围整理在表中。
可以看出账号所具有的数据权限是由账号所在组织节点及所属角色决定的。如果你能够理解这个案例中不同账号所管理的数据范围,那么说明你掌握了业务系统数据权限管理体系的核心思想。
对于“账号4”,你能否判断出它能查看哪些门店的订单数据呢?
由于“账号4”在广东机构下,数据权限范围是当前节点及其子节点,所以“账号4”能访问的数据范围是“门店2、3、4、5”。
通过组织机构树来管理数据权限,是业务系统设计中的常见做法,务必理解其设计原理和设计思想。
除此以外,对于成熟的商业软件产品,还可以做更精细的设置,例如,可以针对实体甚至是字段层面定义数据处理权限,以及定义用户对自己创建的数据、自己拥有的数据的管理权限。
三、权限组
用户可以分组,权限同样也可以分组。
在有的权限系统的中,为了方便用户的使用,还衍生出了权限组这样的实体模型。权限组是一类权限的聚合,这样的权限可能是API接口或菜单等。
比如用户操作权限组就可以包含对用户数据的增删查改操作,使用权限组的时候,角色不是和api这样的权限去绑定,而是去和权限组绑定。
在权限特别多的情况下,可以把同一层级的权限合并为一个权限组,如二级菜单下面有十几个按钮,如果对按钮的操作没有角色限制,可以把这些按钮权限与二级菜单权限合并为一个权限组,然后再把权限组赋予角色就可以了。
给权限分组也是个技术活,需要理清楚权限之间的关系,比如支付的运营后台我们需要查各种信息,账务的数据、订单的数据、商户的数据等等,这些查询的数据并不在一个页面,每个页面也有很多按钮,我们可以把这几个页面以及按钮对应的权限组合成一个权限组赋予角色。
“菜单管理”是精细到系统中最小的操作,可以直接用于分配角色的权限,但如果为了提升用户体验,需要给用户展示出合并后的功能权限时(见下图),则不可在“菜单管理”中直接设置“查看与操作”的按钮,而应该在“菜单管理”之上建一层专门用于展示给用户的功能权限,其中“查看与操作”权限绑定了“新建+编辑+删除”的按钮。
其实建立权限组,就是将不必要的权限进行合并,可以简化用户操作。
例如,全球知名厂商Sales Force的CRM产品Sales Cloud就遵循了经典的RBAC权限设计思路,实现了用户、角色、权限组的管理;同时实现了更灵活的配置功能,可以对页面、视图甚至排版布局等进行详细且全面的个性化定制。
图中呈现了Sales Cloud对基本角色属性的权限管理模块,可以看到,除了页面、控件级别的设置,还可以对系统内的每个业务对象进行读取、创建、编辑、删除等权限的控制,这里的业务对象包括业务机会、个案、价格手册等,这些都是CRM系统中常见的业务对象,相当于分销平台中的机构、门店、报价单等业务对象。
四、权限设计
(一)权限的划分原则
1.最小特权原则
先举一个反例,我把系统中所有的元素和操作都组合成一个权限。
一个用户拥有这个权限就相当拥有了系统所有的功能,实际上这肯定是不行的,用户在一套系统中一定有他不允许操作的内容,哪怕是超级管理员也可能会有不能操作的元素,那么最大化权限则是行不通,因为不符合常理。
据此,我们就把权限再进行一个拆分,按照业务模块进行拆分,但是这实际上也是不行的。
就比如系统中的财务模块,假定模块中含有报销页面和申报页面,如果按照模块进行拆分,那么肯定有用户同时包含了两个互斥功能。
根据上面的原则,我们需要按照最小化进行权限划分。但是这个也是值得商榷的,因为不同系统,最小的权限划分对于提供的功能来说,划分的角度也是不同的。
2.数据抽象原则
“最小特权划分”从某个程度上来说决定了控制的对象 ,而数据抽象原则是是决定了操作。
数据抽象从字面的意思来看,其实很难理解到底是什么意思。通常我们口头上说最多的是CRUD增删查改,这实际上就是数据抽象的一种,我们可以理解成元素操作许可权的意思。
但是CRUD并不是数据抽象的全部,增删查改用于单实体,基本是没问题的,但是在构建关系上,其实是不够用的,例如任免某个经理管辖某个部门,从业务表面而言它修改了经理的管辖范围。
但是从代码底层构建上来说,它属于在经理和部门间新增了一道关系,所以根据需求我们需要再额外的增加一类许可权“任免许可”,这一类型的扩展则需要根据系统实际的业务情况进行划分。
3.服务端的权限
“最小特权”和“数据抽象”分别决定了权限中控制的对象和操作,但是这里面还差了一个角度,则是现阶段非常普遍的前后端分离的权限划分的问题。
前后端分离的架构,服务端的权限具有以下特点:
- 前后端分离下的服务端,本质而言只是提供接口的或者rpc服务等其他资源服务的服务提供方。
-
服务端能提供的权限的鉴权机制的对象:接口服务(api或者其他形式的服务)不包含前端的页面或页面中的功能点。
-
前端或移动端的页面元素的控制和鉴权实质上不由服务端控制。
-
服务端可以单独的控制服务的权限。
-
服务端的服务对象是前端、移动端、第三方客户端,提供的服务是接口服务。
前端或移动端的权限的特点:
- 前端的鉴权包含页面的可访问和页面上的某项功能按钮是否可以操作。
- 前端和移动端的服务对象是用户,提供的服务是可视化的页面。
前后端的服务对象的责任划分清晰之后,我们就不会混杂权限的归属的问题,在过去前后端没分离的情况下,页面本身就是服务端的一体,就没有这方面的问题。虽然分清楚了各端本质提供的服务的情况,但是前后端分离的权限划分中仍有新的问题。
因为服务端和前端的鉴权对象不一致,服务端只能鉴权到api接口,那么是否将api接口和前端的页面乃至页面功能点进行数据库表与表层面的绑定关系?
如果进行了进行了表与表之间的绑定关系,那么整个权限系统的维护量,是否能在能承受范围之内?
如果不进行表与表之间的绑定关系,前端页面在操作功能的时候,服务端如何鉴权页面调用的api接口是否在用户可操作的权限之内?
其实上面的问题则需要一个取舍,要么增加运维成本严格控制前端调用api接口的关系,偏重服务端的接口服务鉴权。
要么是给api接口和前端页面及功能点再提供一个通性的逻辑判断处理,如:页面及调用的功能点属于某个业务模块的操作许可,而页面触发的接口也刚好是这个业务模块的操作许可,那么鉴权通过,否则鉴权失败。这种就是属于侧重前端对于用户的控制,弱化了接口级的控制。
(二)权限设计的注意事项
1.唯RBAC论
尽管RBAC已经被广泛应用,但还是要避免一上来就这么做。权限的设计需要评估数据量和业务复杂度,如果预估的数据量特别少,业务又很简单,那完全可以用其他方式。
2.唯自由配置论
不要认为把权限设置得灵活可配用户就满意了,实际上用户没有那么聪明,他会为角色名称而纠结,看到一系列的权限配置也会不知所措。如果你的业务能够抽象出一些固定角色和统一权限,那就把他们内置起来。
3.权限越多/越细越好
权限不是功能列表,一味的细化而不注重实际业务,只会增加开发工作量,也不利于用户配置。可以仅选择常用的功能点作为权限配置,也可以将多个功能点抽象为一个权限点,如将“增删改”三个功能抽象为一个“管理”权限点。
五、数据库表设计
在RBAC设计的权限管理系统中,在关联权限表一侧的角度来看分为两种设计:分离式设计和合并表设计。
(一)分离式设计
在前面我们了解到权限由受控对象和操作构成,那么数据库表的设计分为受控对象表、操作表和权限表就好理解了。
在此数据库表设计中,无论有多少种资源类型,比如接口、文件、页面等,都可以做到扩展,具有很高的灵活性。
缺点就是需要关联查询很多表,性能较差,维护起来比较复杂。
1.权限表
权限表是否有必要存在?
这个要结合系统的实际使用场景进行考虑,如果系统中的权限的对象很单一,比如只有页面,或者只有api接口的话,其实权限表可有可无。
增加权限表反而会导致初始化项目权限的工作量增加。但是若系统中的权限对象是多个,那么权限表的存在就有了更深层次的意义。
在权限对象是多个的情况,权限表的存在就是为了更好更抽象的组合“最小特权”及“责任划分”的操作对象。
同时,一旦系统中的操作对象增加了,只需要给权限表增加一个对象表和关系表就可以了。这样易于扩展。
2.接口和页面
API接口和页面是否要绑定?
api接口和页面实际上是没有关系的,但是在鉴权活动是有关系的。
页面若和api没有一点绑定联系的话,服务端接口调用的时候则要么拦截掉所有指定的接口,服务端接口完全不拦截接口,也会不安全,但是api接口和页面功能在表结构层面的绑定会产生运维的大量工作成本,如何更好的设计?
其实,在表结构中,我们可以增加一张业务模块表和操作表(也可以在数据字典表中增加这两类数据),我们可以在页面和功能点中绑定业务模块和操作表关系。
在api接口的代码层面去绑定业务模块和操作,在逻辑上绑定关系,解耦表结构之间的关系,那么可以在一定程度上解决这一点,这样做只会出现一种问题,那就是用户访问页面的时候可调用的api接口会比实际可调用的接口数要多,但是前端权限管理会隐藏功能点,这样就在可视化的程度上解决了这个问题。
(二)合并表设计
RBAC模型可以简单地抽象为ER图,即每个用户都要被赋予一个或多个系统角色,每个系统角色都对应一个明确的权限集合,包括对菜单、页面元素等资源的访问和操作权限。
当为小型软件系统设计权限管理系统时,权限表不需要那么复杂,可以简化成一个菜单表。
菜单表控制整个系统中的页面资源和按钮资源。
页面资源通过URL控制,在前端通过URL的授权访问列表控制页面的显示和隐藏。
按钮资源依附于页面资源,每个按钮都有个授权编码,通过该授权编码不仅控制按钮的显示和隐藏,同时作为访问服务端接口验证的依据。
这样一个通过页面+按钮模式的权限管理系统就构成了!
1.菜单管理
“菜单管理”又被称为资源管理,是系统资源对外的表现形式。有了“菜单管理”,不仅可以让权限的管理更简单(不需要后端写死),还能够让系统展示更灵活(能够改菜单信息)。“菜单”分为三类:
- 目录:在系统内没有实际页面
- 菜单:存在实际页面
- 按钮:菜单中的可操作项
设计“菜单管理”需要注意以下细节:
- 菜单类型分为目录、菜单、按钮三种类型。
- 导航菜单是分上下级的,因此可以用树状图表示,点击目录不跳转页面,点击末级菜单项才跳转页面。
- URL路由地址除了系统内前端页面,也可以设计外部访问的地址,访问外链。
- 每个菜单除了给出URL路径之外,应该给个菜单路径的标识name方便页面跳转。
- 菜单表中的隐藏按钮是控制菜单项隐藏的,因为存在菜单项不显示,但是页面需要有权访问的情况,比如订单详情。
- 按钮的权限标识既控制按钮的显示和隐藏,也是接口验证权限的凭据。
- 是否外链、启用状态和创建时间,除了图中展示的字段,根据业务需求还可以增加“编号、是否打开新页面、是否隐藏、是否缓存(切换到其他菜单当前菜单会进行缓存)”等字段。
例如,在开源的人人权限管理系统中,菜单表的数据是这样的。
在合并表的设计中,尤其要注意菜单表中URL路径所表达的意思!
URL是页面访问的相对路径,在vue-route做路由访问控制时,需要满足vue-route中path的格式要求。
同时,组件component也是根据url路径指向页面文件地址的,例如/sys/user的URL路径,必须有/views/sys/user.vue的页面文件。
而且菜单栏又需要渲染URL的路径,需要满足跳转页面的格式要求。
这意味着什么?
意味着菜单的路径你不能随便命名,比如/user/info/:id、/user/info?id=3&type=edit这样的命名不符合上面的格式要求。除非你做特别处理!
菜单栏指向的页面通常是权限管理、部门管理、文章管理等“管理”的路径,URL指向的页面是一个“概括型”的页面,一般是列表页,很少是添加权限、添加部门、添加文章等“动作型”的路径。
因为各个按钮需要挂载在这种概括型页面的下级,通过这种页面入口来控制权限通往各个增、删、查、改的页面。
例如,用户详情的页面是通过查看按钮控制进入的,但是用户详情的页面又需要做权限控制,否则用户知道URL路径也会越权进行访问。
同时,查看用户的按钮不是用户详情的下级,所以用户详情的页面的下级没有任何按钮。
若用户详情需要被访问,但是不需要显示在菜单栏中,则该页面需要被授权但被hidden隐藏。
2.案例解释
如表所示,展示了某公司的分销运营管理后台功能权限表。
其中,“一级导航”列代表页面所在的一级导航目录,如果系统存在二级导航目录,则可以加入“二级导航”列;“页面”列代表某具体页面;“页面元素”列代表某页面中需要实现权限控制的功能点。
现在,我们针对“分销-管理员”和“分销-运营人员”这两个角色梳理功能权限,见表右侧的两列,其中“√”表示可以访问或操作,“—”表示不可以访问或操作。
分析
- 第2行,“门店列表页”这个页面可以被两个角色访问。“门店列表页”上列出了各个门店的名称,每个门店名称都是一个超链接,点击会跳转到相应的“门店详情页”。
- 第3行,“门店列表页”的“编辑”按钮,可以被两个角色操作。
- 第4行,“门店列表页”的“删除”按钮,允许“分销-管理员”操作,不允许“分销-运营人员”操作。
- 其中,第9行和第10行比较特殊,这两行的“一级导航”列为空,表示“门店详情页”和“门店创建/编辑页”这两个页面在导航菜单中不可见,但可以被两个角色访问,具体的页面入口是“门店列表页”上的各个门店名称的超链接,以及“创建门店”按钮。
既然各个门店名称的超链接和“创建门店”这个按钮已经通过权限配置实现了针对不同角色的可见或隐藏,那么为什么“门店详情页”和“门店创建/编辑页”还要再做一次页面级别的权限控制呢?
我们以“创建门店”按钮为例,除了控制按钮在列表页是否展现,还要控制是否允许用户访问“创建门店”页面的URL,否则如果用户知道页面的URL,即便看不到按钮入口,也可以访问页面。所以,页面级别的权限控制也是有必要的。