A Role-Based Access Control (RBAC) system for PHP

A Role-Based Access Control (RBAC) system for PHP

PHP基于角色的访问控制系统设计

By Tony Marston

13th May 2004

Amended 9th March 2008

介绍

“访问控制”系统,即“安全系统”,或“权限系统”。在我长期的工作中我曾经参与设计和开发了几个这样的系统:

  • 20世纪80年代我用古典的COBOL设计编写了一个“菜单安全系统”。
  • 20世纪90年代我又用很少人知道的第四代语言UNIFACE编写了这个“菜单安全系统”。
  • 2003年我使用PHP+MySQL重写了这个”菜单安全系统“用于管理Web应用程序的安全。

这篇文章将描述一些早期我使用过的系统中的特点,并解释我当前的设计的主要特性。

什么是“访问控制”?

在单用户应用程序如典型的桌面应用程序中不需要任何访问控制,用户可以访问程序的全部功能。然而,当一个程序被部署到多台联网的机器上,并被多人使用时,不是所有人都拥有是用应用程序所有功能的权限了。这种情况下,就需要一个适当的方法来控制某些功能只能让被授权的人访问。为了实现这样的需求,以下几点是必须的:

  • 应用程序所有可用功能列表。这些功能有时候也被称作“事务处理”或“任务”。
  • 所有可以访问应用程序的人的列表,这些人有时候也被称作是“用户”。一个典型的例子是,这些“用户”信息广泛地被用在登录处理中,只有登录后用户才能访问应用程序的其他部分。
  • 描述用户可以访问功能的权限列表

以上列出的每一项一般都是由数据库的一个表来维护。

什么是“基于角色”?

实际上我们有很多方法来给不同的用户授不用的权限,但每一种方法都有优缺点。下面讨论我所遇到的各种授权方法。

基于级别的访问控制

基于级别的访问控制的一个仅仅需要两个数据表的简单系统,USERS表和TASKS表,它们之间没有任何关系。如图1所示。

图1 - 基于级别的权限系统

rbac-01

在该系统中,每一项任务(TASK)都会被赋予一个从1-99的安全级别编号,1是最低级别,99是最高级别。然后每个用户(USER)也被赋予一个安全级别的编号,用户只能访问小于或等于该安全级别编号的任务(TASK)。也就是说一个拥有安全级别编号5的用户可以访问安全级别编号是1-5的任务(TASK)。

这个系统的问题在于它是一个完全的累计系统,通过获得更高的编号就能获取更多权限,而且只能通过降低编号来去除权限。共享一个级别编号的一组任务只能全部拥有或全部不拥有该组任务的权限,没有办法使某个用户拥有该任务组中部分任务的权限。例如,有“A”和“B”两个用于,“a”和“b”两个任务,现在尝试使用户“A”可以访问任务“a”但是不能访问任务“b”,使用户“B”可以访问任务"b”但是不能访问任务"a”,你会发现这是不可能的:

  • 如果两个任务有相同的安全级别,那么对用户来说,要么全部可以访问,要么全部不能访问。
  • 如果一个任务的安全级别比另一个任务低,那么用户要么只能访问安全级别低的那一个任务或这是两个都可以访问。

基于用户的访问控制

在这个系统中权限是面向单个用户的。这些权限包含了用联接或交叉表表示的用户与任务的多对多的关系,如图2所示。

图2 - 基于用户的权限系统

A Role-Based Access Control (RBAC) system for PHP_第1张图片

我的实现有些不同:

  • 在一个有复杂任务的系统中,单个任务可以通过创建,读取,更新和删除模式来进行操作,对任务的访问将包含这些模式。
  • 当通过当个模式访问任务时,权限记录就需要一个是或否的开关来对应每个访问模式。通常被称作CRUD矩阵(“CURD”表示创建,读取,更新,删除操作模式),任务行和权限列就像矩阵一样。
  • 在只有简单任务的系统中,每一个访问模式都可以被看作一个完全独立的任务,那么就不需要CRUD矩阵了。

我个人比较喜欢仅包含小而简单任务的系统。这可能会增加任务的数量,但是它们每一个都是简单的,容易设计,容易定制,容易开发,也容易使用。

基于用户的访问控制系统的缺点在于多个用户共享相同的权限时,权限的任何改变都必须给每个用户重复指定。

基于用户组的访问控制系统

在这个设计中用户被分配到用户组中,权限被赋予用户组,而不是单独的用户,如图3所示。

图3 - 基于用户组的权限系统

A Role-Based Access Control (RBAC) system for PHP_第2张图片

这种设计有以下优点:

  • 一旦用户被确认,用户记录将提供一个用户组标识用于访问权限表。
  • 对用户组权限的任何改动都自动被用户组成员继承。
  • 改变用户组权限可以很容易,因为只有一个表需要维护——权限表。
  • 如果单个用户被分配到了别的用户组,新用户组的权限将立即替代旧用户组的权限。

在这种设计中用户组表有时被称作安全类别(SECURITY-CLASS )或角色。

基于职责的访问控制系统

在这种设计中,用户可以同时属于多个用户组。同样包括图4中的多对多的关系。

图4 - 基于职责的访问控制系统(简单版)

A Role-Based Access Control (RBAC) system for PHP_第3张图片

用户组表有时被称作职责区域,因为单个用户可以对多个区域负责。

这种设计的缺点有:

  • 可以通过将用户与用户组关联来授予用户权限,不能通过添加用户组来取消一个已存在的用户组的权限。
  • 现在用户要想访问任务,就有两个表,USER-USER-GROUP 表和TASK-USER-GROUP 表需要维护了。

图5是这种设计更复杂的版本

图5 - 基于职责的权限系统(复杂版)

A Role-Based Access Control (RBAC) system for PHP_第4张图片

在这种设计中,有5个多对多的关系,可以支持大量的自定义。在实现中,我发现任务是复杂的(单个任务可以CRUD),这意味着每个关联或交叉表都会是一个CURD矩阵。当这些表被读入到一个严格的序列中,在一个表中的任务权限可以被在另一个表中的权限替代。所以一条数据记录的权限标记ON有可能将被来自另一个表具有相同取权限但是标记为OFF的数据记录废掉。

即使在理论上这种设计的出现会带来更多的灵活性,实际上会产生了一个可用性问题。权限存在在5个表中,一个表所拥有的权限可以被其他表的内容取代,这就很难跟踪到哪个用户访问了哪个任务。

什么是“菜单系统”?

在一个包含数个甚至是上百个功能的应用程序中,就必须有一个方法使用户快速地找到他想要的功能。在我20世纪70年代最初的系统中,这个“清单”实际上远远比不上一份操作手册,系统的每部分必须从命令行手动激活。后来命令清单可以通过一种机制显示在计算机屏幕上,这样只需要简单地选择一项就可以激活一个功能了。 在大型系统中这样在一个单元上进行选择却是很繁琐的,所以功能清单又被分成了更小的单元或页面。这些页面按照某种层级结构排列,在一个页面上的链接可以激活其他更低级别的页面。这就是菜单系统的由来。

在我最初的菜单系统中,菜单页面总是在一个严格固定的结构中进行硬编码。这种方法的缺点显而易见:

  • 菜单页面得在开发开始前就设计构造好。
  • 修改菜单页面就必须修改和重新编译一段程序代码。
  • 每个用户登录后只能从相同的页面开始,即使他可以访问的选项都在一个子菜单中。
  • 那些不能被用户访问的选项仍然会出现在用户的菜单屏幕中。
  • 上下文信息不能从一个页面传递到另一个页面。这意味着如果用户找到一条数据,需要转到另一个页面上查看这条记录的详细信息时,用户只能在第二个页面手动输入这条记录的ID。这样明显影响效率,而且容易输错。

在20世纪80年代中期,我奉命改良菜单系统,克服以上缺点,就有了下面的东西:

  • 我添加了一个“菜单”表到安全数据库中,这样菜单就可以从数据库动态构建了。数据库已经包含了以安全为目的的事务,我就可以放心地使用相同的数据来定义和构建菜单页面了。
  • 这样一来,菜单页面不需要程序员参与就可以创建、修改。也不需要在项目开始前定义整个菜单结构了,因为它现在可以在空闲时间修改了。
  • 我允许只有子菜单权限的用户登录后只显示子菜单,从而越过那些不必要的(没有权限的)菜单。
  • 菜单系统从数据库构建,数据库本身也包含用户访问权限控制,所以我创建了一个可以被所有用户共享单个的集合来替代那些给每个用户独立的菜单集合。
  • 我创建了使上下文可以在多页面间传递的机制,从而避免了手动输入数据记录ID。

就像你刚刚所看到的,安全系统和菜单系统共享许多公共数据,这对将它们组合成一个系统很有意义(至少对我是这样的)

我目前的设计

我目前的设计经过了20多年的发展,已经用3种不同的语言实现。它们被用作许多不同客户不同系统的关键部分,也被证实是有效而强健的。它由数据库内容驱动,所以对它的改动很容易也很快。考虑到模块化的设计,任何功能改变都可以很容易,只需要修改现有的模块或添加一个新模块。

我选择实现基于用户组的权限系统,围绕USER<==ROLE==>ROLE TASK<==TASK 表,可以提供足够的灵活性而不需要基于职责的安全系统。这样做有两个重要的原因:

  • 权限在一个页面上就可以维护。
  • 可以在运行时确认权限,只需要使用ROLE_ID和TASK_ID进行一次对ROLE-TASK表的查找即可。

图6 - 我目前的设计

A Role-Based Access Control (RBAC) system for PHP_第5张图片 

下面解释一下数据库中的其他表:

  • PATTERN - 每个任务符合Web应用程序事务模式的一种,这作为没个TASK记录的标识非常有用。举个例子,从ROLE-TASK表(权限表)中查询,我可以很快地将要选择到条目按任何不同的类型分组。
  • SUBSYSTEM - 每个应用程序或系统常常可以分解为离散的几个部分或子系统,这些子系统可以被当作独立的组件集合。例如,“菜单和安全”分开“工作流”,“工作流”分开“产品”,“产品”分开“客户”。这十分常见,一个用户将只会对这些区域中的一个负责,因此,当维护访问权限时,子系统作为选择标准的一部分是很有用的。
  • MENU - 有“菜单”性质的任务项需要在MENU表上进行维护。 当用户选择了一个菜单,系统就会检索并显示MENU表的内容。通过ROLE-TASK表,对于用户没有权限的菜单将不会显示,从而只显示用户有权限访问的菜单。MENU表的内容显示在Web页面的菜单条(menu bar)上。
  • NAVIGATION BUTTON - 有些任务不需要任何上下文信息就可以访问,可以放置在任何菜单屏幕上。然而,在我的基础结构中有一组由一个父表单和多个子表单组成的表单结构。父表单可以房子任何菜单上,父表单没有激活时子表单是不可用的。因为子表单需要由父表单提供上下文信息。例如,在跳转到UPDATE,ENQUIRE或者DELETE表单前,你可以在一个列表表单中选择一个或多个列表项。这些子任务在它们自己的数据表中定义,显示在它们自己的导航条(navigation bar)区域中。
  • TASK-FIELD 和 ROLE-TASK-FIELD - 有时候我被要求提供一个以只读或不可见的方式显示单个字段的屏幕。在某些语言中,不可能动态地改变字段呈现方式,在这种情况下,不得不重新创建一个完整的页面。然后,每个Web页面都是从XSL转换来的HTML文档,我提供了一种使可以向转换过程传递摘要信息来修改单独字段的转换方式的机制。默认情况下,每个用户拥有访问屏幕上所有字段的权限,但是在TASK-FIELD表可以定义“例外”字段,通过在ROLE-TASK-FIELD表修改字段的权限。这就使得单个页面可以在不同用户访问时以不同的形式显示,字段的定义就被动态地修改了。
  • HELP-TEXT - 在每个页面上的任务都会有一个指向当前任务的单独帮助信息链接。可以从数据库取出帮助信息然后通过标准帮助程序提供给用户。这些帮助信息也可以包含到其他文档的链接。

结论

有些人可能会说这么“简单”的需求就需要维护那么复杂的数据库啊?但是我的长期经验告诉我,可以总是认为原始的设计无法满足客户们一点点额外的需求。现在你很容易就可以改变现在的系统来实现下面的“小小需求”了:

  • 我可以动态地修改菜单而不需要修改程序代码吗?
  • 我只想显示我可以访问的菜单。
  • 我想要在单个屏幕上对某个用户改变某个字段的访问方式。

这么些年以来,通过这个数据库我可以轻松地扩展它以实现新的需求或特性。比如,通过TASK表,我可以将任务的一些额外的信息包含进来,这些信息可以被用在任务运行时,也可以修改这些信息而不用修改代码,就像之前的列表页面排序命令一样。

PHP+MySQL版本的文档在这里可以找到:User Guide to the Menu and Security (RBAC) System。

其他类型的访问控制

本文所描述的访问控制系统在应用程序内授予或拒绝使用指定任务的权限,如果一个用户被授予访问某个任务的权限,那么他将自动获得访问该任务相关数据的权限。例如,授予“更新客户详细信息”任务的权限就意味著用户可以更新任意客户的详细信息。

一些用户可能要求一个超越单独任务层次上的,应用在数据层次上的权限等级。比如下面的场景:

  • 应用程序数据库通常保存单个组织的数据,所有的用户都属于这个组织,所以全部用户都可能会要访问到所有的数据(所拥有权限的任务主题)。在某些情况下数据库可以处理多个组织,但是每个用户只能访问自己的那个组织的数据。比如组织#1的用户拥有“更新客户详细信息”任务的权限,但是只能更新组织#1的客户数据。其他组织的客户数据对他来说是不可见的。这样的需求,实际想就是我们熟知的虚拟私有数据库(Virtual Private Database,VPD)或者是行级别安全(Row Level Security,RLS),可以使用Radicore框架来实现,它有一篇关于实现虚拟私有数据库的介绍。
  • 一个用户可以访问任何组织的数据,但是对不同组织具有不同的权限。这是上面需求的一个变种。例如。他可能在组织#1中拥有“更新客户详细信息”的权限,但是在组织#2中没有。每个用户对不同的组织分别进行登录就可以满足这样的需求了。不过如果不同组织分开登录是不能接受的,那么就需要修改应用框架来对每个不同组织设置单独的权限集合给每个用户了,并却要修改登录页面,包含用于标识将要访问哪个组织的字段。

 

© Tony Marston
      13th May 2004
      http://www.tonymarston.net
      http://www.radicore.org


你可能感兴趣的:(数据库,Security,System,Access,任务,menu)