基于角色的访问控制设计思想
分析访问控制的一般设计思路,提出一套基于角色的访问控制的设计思路,并使其成为一个模块加入到系统中使得系统能实现为不同角色的用户提供不同的权限并进行验证等功能。
有这么一个案例:国内有一家大型知名医药企业,它们使用了一套企业管理系统, 总公司经理 用自己的账户登录后能进行 查看企业销售报表 , 审核订单 等操作,而 区域销售代表 用自己的账户登录后能够使用该系统进行 客户信息维护 、 为客户下订单 、 提取预付款 等操作,在公司总部大楼内, 财务部会计 用自己的账户登录后可以使用 帐务结算 、 工资发放 等操作…
在这套系统中,区域销售代表是无权查看企业销售报表,也无权进行审核订单操作的,其他人也类似,整个企业的所有员工在该系统中都各司其职,都无法越权使用超越自己职责范围的操作。甚至他们各自进入系统所能看到的界面都不尽相同。这对该系统来说,它就必须要有一个判断逻辑:主体、行为、对象,也就是说 谁能做什么事 或者 谁不能做什么事 。
本文将和你一起讨论该访问控制模块的设计思想,首先将会提供一些模型并加以分析,然后一步步改进,最后得到一个小型但是比较完整的模型。
注意本文所实现的模型并不是完整意义的访问控制系统,它仅仅实现了其中的一小部分,它只解决一些粗粒度的权限,也就是仅仅告诉系统谁能做什么事或者谁不能做什么事。从程序的角度来讲,它只是以能为上层的访问控制系统提供服务为目标。 相当多细粒度的权限问题因其极其独特而不具通用意义,它们被看作是 业务逻辑 的一部分。比如,要求: 合同只能被它的创建者删除,与创建者同组的用户可以修改,所有的用户能够浏览 ,这既可以认为是一个细粒度的权限问题,也可以认为是一个业务逻辑问题,在整个权限系统的架构设计之中对其不予过多考虑。
当然,权限系统的架构也必须要能支持这样的控制判断。或者说,系统提供足够多但不是完全的控制能力。 系统只提供粗粒度的权限,细粒度的权限被认为是业务逻辑的职责 ,它不提供所有关于权限的问题的解决方法,只提供一个基础,并解决那些具有 “ 共性 ” 的 ( 或者说粗粒度的 ) 部分。
功能分析
在 总公司经理张三审核订单 中, 总公司经理 是一个角色, 张三 是一个用户, 订单 是一个资源, 审核 是对该资源的一个行为。如果只说审核,这是无意义的,当它结合了一个特定资源的实例(订单)时,可以称为这是一个 权限 ,也就是说, 审核订单 是一个 权限 。对于 总公司经理 来说, 审核订单 这一权限是得到 许可授权 的,当 张三 得到 总公司经理 这一角色的任命后,它就得到了 审核订单 这一权限的 许可授权 。当然, 张三 既然是 总公司经理 了, 总公司经理 的权限有一大堆,比如 查看销售报表 ,所以 张三 还能进行 查看销售报表 等操作。当然还有其它几个人也具有 总公司经理 这一角色,所以他们能进行和 张三 同样的工作。
于是,对于 张三 ,它通过 总公司经理 这一角色获得了属于它的一系列 权限集 ( 审核订单 、查看销售报表 等)。
这样就形成一个映射关系:
从张三到他的权限的映射关系
如图所示, 用户 和 权限 被隔离开来,中间插入了一个 角色 的概念, 用户 只有 绑定 了某一 角色 后,才能获得该角色所对应的 权限集 。这就是本文的核心思想。
有人可能会提出这样的问题:为什么不直接将各个权限指派给具体用户呢,如下图所示,这样可以更加灵活地为每一个用户定义其权限。
从张三到他的权限的映射关系(排除角色)
实际上这样做是可以的,当然也有很多项目是这样设计的。但是要考虑到两个方面:
存在整个系统中各部分随时间发生变化的概率导致的维护难度。例如:系统增加新功能,人员调动,公司业务扩大而增设新岗位,新员工上岗等,都可以产生对用户的权限重新分配的需要;这时就带来对涉及到的用户进行一定的权限分配的维护的复杂性。
存在为大量用户进行权限分配时带来重复操作的可能性。
当系统中权限相对较少,并且用户数量不大的情况下,这种方法还比较可行,但是随着用户数量发生大量的变化,或者其它原因导致权限数量的扩大,大量重复操作带来的时间上的浪费将是不可原谅的。
设想一下: 张三 因某种原因离开公司,在办理辞职手续的时候要 取消 掉他所有分配到的权限,同时将其 总公司经理 的权限分配给 李四 ,但是李四仅仅临时担任了一天,董事会任命的 王五 就正式来上任了,紧接着公司的 区域销售代表刘六 报告说人手暂时不足,需要抽调人力临时帮助一下 …… 天!为了给他们分配相应的权限,光是这些点鼠标的操作就够你忙上一阵的了。而且都是重复性地操作。如果正好碰到上司正在为你的工作效率发脾气的话,可能你连这个季度的奖金都会泡汤掉的。而这个时候你甚至可能会认为电脑办事情其实不如人快呢。
但是如果考虑一下角色会怎么样? 张三 辞职的时候,把他所 绑定 的 总公司经理 角色取消掉,然后找到 李四 这个用户,给他绑定一下这个角色, 王五 来上任后,再 取消 掉李四的这个角色,给 王五 绑定上,当 区域销售代表刘六 打电话来要求人手的时候,将上司列出的名单上的人找出来,把 区域销售代表 这一角色拖过来,一点鼠标,咔擦几下搞定!整个过程不过三五分钟甚至更少,而且不需要去看每个人需要有的权限是哪些,呵呵,这个管理员当得很轻松。
考虑到这些问题,对于基于角色的这一方案来讲,还是有其适用范围的,因为 角色 相对于用户来说发生变化的概率要小得多,同时它减少了用户和权限这两个变化概率较大的部分之间的耦合度,因此会带来一定的便利。
现在言归正传,上面提到的两种解决方案有其各自的应用范围,本文所建议的基于角色的方案是基于有一定数量的用户群体和一定数量的权限来考虑的。本文将设计一个基于角色的访问控制模块,它向其它模块提供如下大致功能:
主要功能
识别当前用户,为其提供当前用户所属的权限集;
判断当前用户对某资源的操作的合法性;权限
对系统中所有资源做一定整理和归纳并储存,可以对其进行检索;
对系统中各资源所针对的行为进行一定整理和归纳并储存,可以对其进行检索;角色
维护角色以及角色与权限的映射关系;
用户
建立并维护用户与角色之间的绑定关系;
下面将对其进行详细阐述。
先列出两个部分: 用户接口 和 权限 。角色呢?这里暂时先放一下,呵呵。整个模块对于使用该模块的其它模块来说,它实际就是一个从 用户 到 权限 的映射关系。如下图所示:
不同的用户分配不同的角色
而对于该模块自身来说,从 用户接口 到 权限 之间的通讯是一个虚通讯 ,中间插入了一个 角色服务 ,对于 用户接口 来说,它的所有请求都发送到 角色服务 ,再由 角色服务 和 权限 打交道,处理结果由 角色服务 发给用户接口。如下图所示:
基于角色的访问控制
用户交互接口是直接面向用户的模块,它负责与角色服务进行直接通信,请求或者响应相关用户或者用户组的资源,以及对所获取的资源进行验证。它主要有三个部分:用户、用户组、校验器用户
这里的用户从狭义上将,它仅仅维护用户的标识并用于绑定角色。而对于帐户密码管理、个人信息管理、安全登录等功能它是不负责处理的。当然它们可以储存在一起,但是这里的用户模块是不对其负责的,因为前面已经限定了该模块实现的功能仅仅是完成用户与权限的映射关系。
很多时候在系统使用一段时间后会出现一些角色变更,例如出现人员调动、部门机构调整等,这时需要给原来的部分用户重新进行角色绑定,在给用户绑定角色的时候,存在一种针对大量用户进行相同的角色绑定操作的可能,因此可以设置一些用户组,并对这些用户组进行角色绑定。在创建用户时就可以选择将该用户归为某一用户组,当出现一次性对某一用户组的用户做大量相同的角色绑定时,就可以选择将这些用户所对应的用户组进行角色绑定,能再次减少一些重复操作的劳累。用户组可以被归为另一用户组,形成树状关系,用户组成为各个节点,而用户是叶子。
用户和用户组的树状关系
一个用户可以被归到多个用户组,而一个用户组可以包括多个用户,这样它们形成一个多对多的关系。如下图所示:
用户和用户组的关系
1 )将这一部分用户解除该用户组的归属并重新归入一个新用户组,然后对该用户组做角色绑定;
2 )单独对每一个用户进行角色绑定。
选择前者的话,可能出现这种结果:用户组的数量比用户数量还要多,并且大多都属于无用的用户组,但是已经分不清哪些是哪些了。如果选择后者的话又可能存在大量重复操作。这是不希望看到的结果。
校验器负责为当前用户请求和检索该用户对应的权限集,并根据一定的授权方式判断当前用户的操作是否合法。它的判断结果是系统授权模块的授权依据,它将判断为合法的用户操作进行许可授权、而判断为非法的用户操作将被拒绝服务。
校验器的授权判断逻辑有两种:正向授权、反向授权。后文对其进行折中后提出一种更为方便的基于安全级别的判断逻辑。
校验器认为所有的资源默认都是授权为拒绝的,只有在该用户的权限表中有对其授权为许可的权限才认为该用户对该资源被授权为许可。而授权为拒绝的权限以及没有授权的权限都认为是拒绝。
校验器认为所有的资源默认都是授权为许可的,只有在该用户的权限表中有对其授权为拒绝的权限才认为该用户对该资源被授权为拒绝。而授权为许可的权限以及没有授权的权限都认为是许可。
基于安全级别的授权判断逻辑
不论是正向授权还是反向授权都是一个系统偏向两个极端的授权方式,在一个稍微复杂的系统中可能需要一种较为折中的方式,这里就提出一个方法:基于安全级别的授权判断逻辑。该方法定义如下:
为系统定义一系列由高到低的安全级别,如定义五种: Highest 、 High 、 Standard 、 Low 、 Lowest ,然后设定一个 当前系统安全级别,假如定义为 Standard ;
为 权限定义一个 访问级别,当该用户的权限集里没有明确定义该权限时,校验器根据其 访问级别以及 当前系统安全级别进行级别高低的判断。如果该资源的 访问级别 不 高于 当前系统安全级别的话,校验器认为该用户对该权限应判断为非法,反之则为合法。
接开篇时的例子,假定该医药企业的系统当前安全级别为 Standard ,有一个权限 审核订单 ,其 访问级别 定义为 High 。对于 刘六 ,该用户没有对这些权限的明确定义,那么在 刘六 进行 审核订单 操作时,校验器发现该权限的 访问级别( High )高于当前系统安全级别 (Standard) ,那么校验器认为 刘六审核订单 为 合法 ,如果将 当前系统安全级别 调节到 Highest 后,刘六再次进行审核订单操作,这时候因为审核订单的 访问级别( High )低于当前系统安全级别( Highest ) ,校验器则认为 刘六审核订单 为 非法 。
如果当前系统安全级别设置到最高级,则校验器的判断逻辑等同于反向授权,反之则等同于正向授权。
将这一模块定义为服务是因为它内部的处理对用户接口是不开放的,它仅仅开放接收用户或用户组标识,处理后响应给用户接口一张权限集。该模块一共分为三部分:绑定、角色、授权。其关系图如下:
角色服务模块关系图
这里要提一点, RBAC_Binding 是绑定表,其中的 ClientID 表示用户标识或者用户组标识,这样使用的前提是用户标识和用户组标识必须 唯一 ,假如用户标识已有编号 1000 ,则用户组中则不能有编号为 1000 的标识。所以 RBAC_Binding 只能认为是一张抽象表,如果是喜欢用自动编号的朋友建议用两张绑定表分别记录用户绑定和用户组绑定,或者将用户和用户组表合并成一张表。
输入客户端标识获得其角色
绑定模块记录用户或用户组标识和角色标识,用于生成并管理 用户或用户组 和 角色 之间的映射关系,向其输入用户标识可以获得其绑定角色的输出。按照企业应用经验,这个绑定可以是无限期的,也可以有一定时效性:
在绑定的同时为其设定一个或多个不交叉的时间段,则当前系统时间落在该绑定在这些时间段内才 有效 ,如果在这些时间段之外则该绑定 无效 。这种绑定需要记录各个时间段标识( 起始时间 、 结束时间或时长 ),对应于绑定表则还需要有一张时间段表来记录时间段,它们是零对多的关系。
时间段绑定
在绑定的同时为其设定一个或多个不交叉的周期性时间段,同样,只有当前系统时间落在当前周期性时间段内该绑定才 有效 。如果在这些时间段之外则该绑定 无效 。这种绑定需要记录各个周期性时间段标识( 周期间隔时长 、 周期次数 、 起始时间 、 结束时间或时长 ),对应于绑定表则还需要有一张周期性时间段表来记录周期性时间段,它们是零对多的关系。同样,周期和时间段是一对多的关系,即一个周期内可以有多个时间段。
周期性时间段绑定
无限期绑定即不限制用户的绑定时效,只需不存在上面几种时效关系即可。
它以一定的规则处理其查询所得的权限资源集,包括整理冗余权限资源、处理权限资源授权冲突等操作。
从用户角度来看,角色就像一个权限资源集的过滤器,通过这个过滤器用户可以获得其相应的权限资源集,将相对于该用户或用户组为非法的访问资源过滤掉。从系统角度来看,系统负责维护一张角色表,记录角色标识、角色名称。
从角色与角色之间的相互影响来看,角色和角色有下面几种关系:
共存关系 。角色和角色之间不存在后面几种关系时便认为它们为共存关系,可以说它们之间没有相互影响。这些角色可以一起绑定到同一个用户 / 用户组。
互斥关系 和排斥关系。角色和角色之间相互排斥时称其为互斥关系,互斥的角色不能同时绑定到同一个用户或用户组,如:会计角色和出纳角色就是互斥关系,这两个角色不能绑定到同一个用户。如果将互斥的范围扩大,一个角色和其他所有角色都是互斥关系的话,便称之为排斥关系,一个用户或用户组绑定了该角色的话,它不能同时再绑定其它任何角色。
继承关系 和概括关系。一个角色继承其它一个或多个角色的内容,我们称前者为子角色,后者为父角色( 可以是多个,但不能互斥),子角色作为对父角色的继承,它将所有父角色的权限合并,可以覆盖父角色的授权。我们说子角色和父角色是继承关系的同时,反过来也可以说父角色和子角色是概括关系。
它负责对指定的角色分配相应的权限并做授权,存放在一张权限表中(记录角色标识、资源标识、行为、和授权方式),并进行维护管理。角色服务以特定的角色列表为条件在角色授权表中做查询得到一张授权资源表。授权所指定的资源同时也包括该资源下所有子资源,除非其子资源有单独的授权定义将其覆盖。
授权
对指定角色的某一权限做允许的授权,也就是它设定 允许 某一 角色 对某一 资源 的 行为 。
对指定角色的某一权限做拒绝的授权,也就是它设定 拒绝 某一 角色 对某一 资源 的 行为 。
对指定角色的某一权限做既不允许也不拒绝的授权。如果是该角色是子角色的话,其最终授权由其各个父角色的授权来判断是否许可或拒绝,父角色也是零授权的话则由校验器根据 当前系统安全级别 来进行实际授权( 正向授权 、 反向授权 )。
每一项权限是由一个特定资源的实例及对该实例的一个行为构成。例如: 订单 是一个 资源 ,对于该资源,有 新建 、 更新 、 删除 、 审核 、 查看 等 行为 ,所以就形成这些权限: 新建订单 、 更新订单 、 删除订单 、 审核订单 、 查看订单 。
权限
注意这里所指的权限是一个粗粒度级的权限。
粗粒度是表示类别级,即仅考虑对象的类别 (the type of object) ,不考虑对象的某个特定实例。比如, 用户管理中,创建、删除,对所有的用户都一视同仁,并不区分操作的具体对象实例 。
细粒度是表示实例级,即需要考虑具体对象的实例 (the instance of object) ,当然,细粒度是在考虑粗粒度的对象类别之后才再考虑特定实例。比如, 合同管理中,列表、删除,需要区分该合同实例是否为当前用户所创建 。
权限逻辑需要配合业务逻辑。即权限系统以为业务逻辑提供服务为目标。相当多细粒度的权限问题因其极其独特而不具通用意义,它们被看作是“业务逻辑”的一部分。比如,要求:“合同资源只能被它的创建者删除,与创建者同组的用户可以修改,所有的用户能够浏览”。这既可以认为是一个细粒度的权限问题,也可以认为是一个业务逻辑问题,在整个权限系统的架构设计之中对其不予过多考虑。当然,权限系统的架构也必须要能支持这样的控制判断。或者说,系统提供足够多但不是完全的控制能力。即,设计原则归结为:“系统只提供粗粒度的权限,细粒度的权限被认为是业务逻辑的职责”。
这里表述的访问控制模块仅是一个“不完全”的访问控制模块,即,它不提供所有关于权限的问题的解决方法。它提供一个基础,并解决那些具有“共性”的 ( 或者说粗粒度的 ) 部分。在这个基础之上,根据“业务逻辑”的独特权限需求,编码实现剩余部分 ( 或者说细粒度的 ) 部分,才算完整。
资源有很多种,如用户界面资源、用户操作资源、网址资源等,这里并不泛泛而谈,仅仅以用户操作资源为主,也就是前文例子中诸如 订单 、 新闻 等。资源创建者将系统中各个面向用户的功能进行分类整理,生成一张资源表,并为每一项资源分配一个唯一标识。
如 图表 8 ? 1 , RBAC_Res 就是一张资源表,其中 ResID 是每一项资源的唯一标识字段,而 DefActionID 指向该资源的一个默认行为,如 查看 。资源可以反向包含自身,形成树状结构。在 RBAC_Res 中有一个 ParentResID 标识指向该资源的父资源标识。对父资源的授权其子资源在同样行为下可以继承,也可以覆盖。
考虑资源的动态变化性(加入新功能、增加栏目等)在有大量角色的情况下为其更新授权不便,这时可以考虑加入域( Domain )的概念,这类似于用户和用户组的关系,将类似的资源进行归纳,划入某一域中,这时候可以以域直接参加授权而不是各个资源.
行为是针对资源的操作方式,如针对 订单 可以有 新建 、 查看 、 更新 、 删除 、 发布 等。
行为可以分为公共行为和私有行为。 公共行为 就是针对所有资源的公共操作方式,如 新建 、 查看 、 更新 、 删除 等。 私有行为 是针对特定资源的操作方式,如针对订单资源除了有其公共行为外,还有 审核 行为等。
结合前文提出一个用户 - 角色 - 权限的查询流程图主线,对于维护相关数据的操作本文暂时不提供。
从用户登录系统伊始,该模块就开始发挥作用,主要过程就是根据当前用户所持有的身份证明(有的文章成为令牌,或者会话)在该模块所管理的所有权限中查询该用户当前的操作是否合法
用户 - 角色 - 权限数据关系图
如果有读者曾经读过阿西莫夫( Issac Asimov )的《基地》三部曲,你可能会发现,整个银河帝国两千五百万个所谓的世界虽然由一个皇帝统治,但是各个单位都是有一定自治权的,毕竟那么大的国家,总部如果什么都要管的话那么它早就忙着崩溃了。
如果一个系统过于庞大和分散,里头有数不清的角色,这时可以考虑一下分成一个个单独的子系统,并将大部分权力下放,同时这个基于角色的访问控制模块也随着下放,让各个子系统行使最大的自治权。而总部仅仅保留一些 过问 的权力。这可能有点不可思议,但是现实中有很多案例的:
微软的 Passport 就是一个庞大的银河帝国,只要你得到 Passport SDK 的授权,就可以让自己的系统使用统一的 MSN 帐户进行登录而不必自行维护用户数据,对于用户来说该系统只要支持 Passport ,那么只要有一个 MSN 帐户就通行无阻。连注册都省掉了,当然一些特定的用户信息录入不可省。如同入境手续一样。
一个门户网站就可能有很多业务系统:招聘应聘、社区、聊天、邮件、新闻、 Blog 、教学、游戏、音乐、下载、交友等等,可能会有几十个(看看国内那几个超级门户网站就知道了),对应的角色有一大堆,是不是很数不清,恐怕连这个网站的管理员都无法说清有多少角色和资源!
但是这是一个自治的例子,总站只负责从子系统提取一些许可的数据,并维护一下单点登陆(系统虽多,但是一个用户一个通行证,各系统根据通行证的级别各自控制访问)、在线支付结算等公共事务,对于总部来说是不是很轻松?他不必去刻意了解该用户是否是邮件系统的 VIP 用户还是普通用户,只需告诉邮件系统:这是我们的人,请根据贵处保留的此人的信息自行处理!
一个子系统中角色数量一般达到两位数就足够了,如果太多的话那么请考虑一下系统的设计是否需要修改一下,划分成一个个单独的子系统再各自考虑。例如:一个大型企业的系统规模过大时可以考虑一下是否可以将邮件、新闻、人事、物流、财务、文件等各个系统划分出来,各自单独使用访问控制模块,并提供一些接口调用,这样不至于造成一个庞大的访问控制系统并造成庞大的维护开销。
当然这些划分并不是一定要在物理上划分不可,把他们放在一起并以不同的标识区分也可以,这要看实际应用的需要了。
新加入一个子系统时并不一定要从零开始设置角色、用户组等初始化信息,经验丰富的设计者或维护人员可以根据以往同类系统的应用精心设计一套或几套 RBAC 模板,新加入的子系统可以直接套用即可,当然可以根据实际情况进行修改,但总比从零开始要快多了。
代文龙:权限系统概要 (http://www.jdon.com)