ThinkPHP与RBAC(基于角色的权限管理)

访问控制可以简单表述为:判断谁(Who)对什么(What/Which)进行怎样(How)的操作是否为真。对于一个系统来说,有必要建立一个良好的访问控制系统,对访问权限进行合理的分配,用于保证系统的安全性、可靠性。

传统的访问控制技术主要有:自主访问控制(DAC)、强制访问控制(MAC)和基于角色的权限访问控制(Role Based Access Control,简称RBAC)。

DAC和MAC访问控制技术均是直接对用户本身进行权限的管理,细度太小。当用户数量庞大并且用户之间关系复杂时,主体和客体关系的匹配及权限的管理就变得复杂起来。并且权限的变更将导致权限分配列表的变更,此时可能会遭遇到很大的困难,甚至于要修改系统的源代码。

而RBAC访问控制技术很好地解决了这一问题。在RBAC中,用户的权限不是在用户本身上进行管理的,用户的权限是由用户所处的角色所决定的。在权限管理中,通过角色这一桥梁将用户与权限联系起来。用户和角色、角色与权限是一个多对多的关系。
与RBAC访问控制相关的概念有:

  1. 用户(User):一个具有唯一标识符的用户,与权限相分离,只能通过所属的Role去关联权限,一个用户可以拥有多项角色;
  2. 角色(Role):一定数量的权限的集合,角色可以继承,一个角色对应多项权限;
  3. 权限(Resource):也可以看作是资源,它对应了应用系统中的一个功能;

下面结合一个实例,阐述以上关系。

在这个实例中共有七项权限,分别是:浏览用户、添加用户、删除用户、添加文档、修改文档、删除文档和审核文档。系统中存在着两个用户和两个角色。如下图所示:

ThinkPHP与RBAC(基于角色的权限管理)

RBAC实例

由上图可知:角色A拥有权限1-3,角色C拥有权限4-6,而角色D仅拥有权限7。由于角色B是角色C和角色D的父亲角色,则可知角色B拥有角色C和角色D 的所有权限,即角色B拥有权限4-7。用户1分别属于角色A和角色B,则用户1拥有权限1-7,而用户2仅属于角色D,由此用户2仅拥有权限7。

按照RBAC的思想,建立访问控制的数据库模型。在本模型中通过四张表格来维护访问控制信息。数据库模型如下图所示:

ThinkPHP与RBAC(基于角色的权限管理)

RBAC表模型

在ThinkPHP1.5框架中集成了RBAC权限管理,而随后的ThinkPHP2.0中取消了RBAC,但是可以通过其他方法予以实现。我们先来看ThinkPHP1.5中的RBAC是如何实现的,其流程图如下:

ThinkPHP与RBAC(基于角色的权限管理)

权限控制流程

在ThinkPHP1.5中,由App类的init方法执行上述流程,其代码如下:

if (C('USER_AUTH_ON')){
   // 启用权限认证 调用RBAC组件
   import('ORG.RBAC.RBAC');
   if (!RBAC::AccessDecision()){
      没有权限 抛出错误
      if (C('RBAC_ERROR_PAGE')){
          redirect(C('RBAC_ERROR_PAGE'));
      } else {
          throw_exception(L('_VALID_ACCESS_'));
      }
  }
}

在这里,由RBAC的静态方法AccessDecision具体负责权限的判断,每一个权限对应了Module里的一个action,当用户所属的角色授权这项权限时,该用户即可访问该方法,否则将转到认证网关(认证页面)中。比如用户A其角色为Publisher,Publisher拥有Article模块的index、add、save、edit、update五个action的权限,则用户A可访问这五个方法,亦即用户A能访问Article/index、Article/add、Article/save、Article/edit、Article/update。

在判断需要验证步骤时,显然需要对哪些模块哪些操作需要验证进行设置。相关配置项为REQUIRE_AUTH_MODULE(需要认证的模块)、REQUIRE_AUTH_ACTION(需要认证的操作)、NOT_AUTH_MODULE(不需要认证的模块)、NOT_AUTH_ACTION(不需要认证的操作)。一般情况下,只需要配置其中一项即可,并不需要全部配置。

在取用户访问决策列表时,需要从数据库中查询用户所属角色所拥有的权限。而权限是由模块和操作决定的。因此需要一张存储模块和操作之间的关系的表,一张存储角色和角色所能访问的操作关系的表。

考虑到系统的各项菜单和模块及模块的操作具有联系,如菜单项“用户管理-添加用户”必然和用户管理模块的添加用户操作相关,而添加用户操作由两步完成:一是提供一个界面供用户输入用户信息,二是处理用户提交的表单并将信息保存到数据库中。因此可以再设一张表,将权限和菜单联系起来,从而实现自动为角色配置不同菜单的功能,实现菜单生成的自动化配置。

因此共需五张表,表和所必需的字段如下:

1. 用户表(user),字段有id、account、password和role字段;
2. 角色表(role),字段有id、name;
3. 节点表(node),字段有id、menu、name、pid和level。level指定了节点所处的层级,层级从1到开始,按照项目、模块(module)、操作(action)依次为1、2、3,项目是模块的父节点,模块是操作的父节点。menu指定了节点所对应的菜单,且仅需对操作级别的节点设置此值,其他级别的可为0;
4. 菜单表(menu),字段有id、pid、name、level、subMenu和menu。如果menu不为空且level=2,则表明该项是可见于菜单中的。对于level为1的菜单均以顶层菜单的形式出现;
5. 可访问表(access),字段有groupId和userId;

通过以上五张表即可完成基于RBAC的权限控制。和ThinkPHP所要求的表略有出入,少了一个用户-角色对应表,多了一张菜单表。在ThinkPHP标准中,支持一个用户对应多个角色,在此处为了降低复杂度,限制了一个用户只能对应一个角色。同时增加了菜单表从而实现用户管理菜单的自动生成。

获取用户的访问决策列表是进行权限验证的必要步骤。获取用户的访问决策列表的方法是:

1. 查询用户所属的角色ID;
2. 根据角色ID从access表中获取该角色所能访问的节点列表;
3. 从node中查询节点列表的相关信息;
4. 对产生的节点列表信息进行处理,生成访问决策列表保存到SESSION中;

最终生成的访问决策列表的形式(三维数组)是:

 
$_SESSION['AppName']['ModuleName']['ActionName']  

AppName是项目名称,ModuleName是模块名称,ActionName是操作名称。如果在SESSION中存在这样的一项数据,即表明用户对指定的“项目-模块-操作”具有访问权限,否则就是没有权限。检测是否有权限即为检测在此数组中是否存在对应项。

在 ThinkPHP2.0中去除了RBAC,但是可以另行扩展以实现RBAC。具体可参考示例代码中的RBAC一项,示例所采用的方法是另行提供一个 CommonAction类,在该类的_initialize方法中进行RBAC判断,即把原Tp1.5中App类的那段权限判断代码移到了 CommonAction中,此处其他Action再继续CommonAction,从而实现了RBAC管理。

affiliate_link
Share this Post:
Digg Google Bookmarks reddit Mixx StumbleUpon Technorati Yahoo! Buzz DesignFloat Delicious BlinkList Furl

Comments are closed.