关于网站用户权限管理的设计模型。 我根据业内的一些标准和案例整理出一套RBAC用户权限设计模型文档。 希望对大家在系统设计时有帮助。 有其他见解请指教。
RBAC权限模型设计
在 RBAC社区的不断努力下,RBAC在 2004年 2月被美国国家标准委员会(ANSI)和 IT国际标准委员会(INCITS)接纳为ANSI INCITS 359-2004标准。 RBAC标准包括两个主要部分:RBAC参考模型和RBAC功能描述。
RBAC 是英文(Role-Based Access Control)的缩写,也就是基于角色的访问控制。RBAC 的定义比较晦涩,我就以比较生动的形式来阐述什么是 RBAC。
ATM 机的一天
假设有一台 ATM(自动提款机)放在街边,我们来看看这个 ATM 度过的一天。
1. 早上,有一个家伙走到 ATM 面前,对着机器说:“芝麻开门,芝麻开门,给我 100 块!”。很显然 ATM 不会有任何动作。失望之余,这个家伙踢了 ATM 一脚走了。
2. 中午,一位漂亮的 Office lady 走到 ATM 机面前,放入她的信用卡,输入密码后,取出了 1200 块钱。当然,这些钱很快就会变成一件衣服或是化妆品。
3. 下班时分,银行的工作人员来到 ATM 机器面前,放入一张特制的磁卡,然后输入密码。从中查询到 ATM 机器内还有充足的现金,无需补充。所以他很高兴的开着车去下一台 ATM 机器所在地了。
现在我们要开发一台具有同样功能的 ATM 机,应该怎么做呢?
首先,我们的 ATM 机不能让人随便取钱,不然银行会破产的。接下来,ATM 机需要一个让人们放入磁卡并输入密码的设备。人们放入磁卡并输入密码后,ATM 机还要能够判断这张磁卡的卡号和密码是否有效,并且匹配。之后,ATM 机必须判断磁卡的卡号属于哪种类型,如果是信用卡,那么则显示查询账户余额和取款的界面。如果是特制的磁卡,则显示 ATM 机内的现金余额。
ATM 与 RBAC
上面的例子显得有点荒诞,但是却是一个典型的基于角色的访问控制。
1. 对于没有磁卡或者输入了错误密码的用户,一律拒绝服务,也就是不允许进行任何其他操作;
2. 如果输入了正确的密码,必须判断用户输入哪一种类型,并提供相应的服务界面;
3. 如果用户尝试访问自己不能使用的服务,那么要明确告诉用户这是不可能的。
这个流程中,一共出现了两种角色:信用卡用户和管理卡用户。而那些没有磁卡的用户,都属于没有角色一类。RBAC 要能够工作,至少需要两个数据:角色信息和访问控制表。
角色信息通常是指某个用户具有的角色,例如你持有一张信用卡,那么你就具有“信用卡用户”这个角色。如果你持有一张管理卡,那么你就具有“管理卡用户”这个角色。如果你既没有信用卡,又没有管理卡,那么你就没有上述两种角色。
有了角色信息,RBAC 系统还需要一个访问控制表。访问控制表(Access Control Table)是一组数据,用于指出哪些角色可以使用哪个功能,哪些角色不能使用哪个功能。例如在 ATM 机中,具有“信用卡用户”角色,就可以使用查询账户余额和取款两项功能;而具有“管理卡用户”角色,就可以使用查询 ATM 机内现金余额的动能。
我们来模拟一次 ATM 机的操作:
1. 唐雷有一张信用卡,他放入 ATM 机并输入了正确的密码。这时,他被 ATM 机认为具有“信用卡用户”角色。
2. 根据上面的判断结果,ATM 机显示了一个操作界面,上面有查询账户余额和取款两项操作按钮。
3. 唐雷按下了“查询账户余额”按钮,ATM 机的查询账户余额功能被调用。
4. 在查询账户余额功能中,再次检查用户的角色信息,确定他可以使用这个功能。
5. 进行一系列操作,然后将唐雷信用卡账户上的余额数字显示到屏幕上。
6. 唐雷很郁闷他的信用卡又透支了,悻悻然取出卡走人了。这时 ATM 自动清除当前的角色信息,为下一次操作做好准备。
从上面可以看出,RBAC 充当了系统的一道安全屏障。所有的操作都需要进过 RBAC 验证过后才能使用。这样充分保证了系统的安全性。
RBAC 概念
在 FleaPHP 的 RBAC 组件中,只有下列几项概念需要理解:
除了上述三个概念,要想 RBAC 系统能够正常工作,还需要用户信息管理器、角色信息管理器和访问控制器三个部件。
FleaPHP核心RBAC文件:
00001
00003 // FleaPHP Framework
00004 //
00005 // Copyright (c) 2005 - 2007 FleaPHP.org (www.fleaphp.org)
00006 //
00007 // 许可协议,请查看源代码中附带的 LICENSE.txt 文件,
00008 // 或者访问 http://www.fleaphp.org/ 获得详细信息。
00010
00030 class FLEA_Rbac
00031 {
00037 var $_sessionKey = 'RBAC_USERDATA';
00038
00044 var $_rolesKey = 'RBAC_ROLES';
00045
00051 function FLEA_Rbac()
00052 {
00053 $this->_sessionKey = FLEA::getAppInf('RBACSessionKey');
00054 if ($this->_sessionKey == 'RBAC_USERDATA') {
00055 trigger_error(_ET(0x0701005), E_USER_WARNING);
00056 }
00057 }
00058
00065 function setUser($userData, $rolesData = null)
00066 {
00067 if ($rolesData) {
00068 $userData[$this->_rolesKey] = $rolesData;
00069 }
00070 $_SESSION[$this->_sessionKey] = $userData;
00071 }
00072
00078 function getUser()
00079 {
00080 return isset($_SESSION[$this->_sessionKey]) ?
00081 $_SESSION[$this->_sessionKey] :
00082 null;
00083 }
00084
00088 function clearUser()
00089 {
00090 unset($_SESSION[$this->_sessionKey]);
00091 }
00092
00098 function getRoles()
00099 {
00100 $user = $this->getUser();
00101 return isset($user[$this->_rolesKey]) ?
00102 $user[$this->_rolesKey] :
00103 null;
00104 }
00105
00111 function getRolesArray()
00112 {
00113 $roles = $this->getRoles();
00114 if (is_array($roles)) { return $roles; }
00115 $tmp = array_map('trim', explode(',', $roles));
00116 return array_filter($tmp, 'trim');
00117 }
00118
00127 function check(& $roles, & $ACT)
00128 {
00129 $roles = array_map('strtoupper', $roles);
00130 if ($ACT['allow'] == RBAC_EVERYONE) {
00131 // 如果 allow 允许所有角色,deny 没有设置,则检查通过
00132 if ($ACT['deny'] == RBAC_NULL) { return true; }
00133 // 如果 deny 为 RBAC_NO_ROLE,则只要用户具有角色就检查通过
00134 if ($ACT['deny'] == RBAC_NO_ROLE) {
00135 if (empty($roles)) { return false; }
00136 return true;
00137 }
00138 // 如果 deny 为 RBAC_HAS_ROLE,则只有用户没有角色信息时才检查通过
00139 if ($ACT['deny'] == RBAC_HAS_ROLE) {
00140 if (empty($roles)) { return true; }
00141 return false;
00142 }
00143 // 如果 deny 也为 RBAC_EVERYONE,则表示 ACT 出现了冲突
00144 if ($ACT['deny'] == RBAC_EVERYONE) {
00145 FLEA::loadClass('FLEA_Rbac_Exception_InvalidACT');
00146 __THROW(new FLEA_Rbac_Exception_InvalidACT($ACT));
00147 return false;
00148 }
00149
00150 // 只有 deny 中没有用户的角色信息,则检查通过
00151 foreach ($roles as $role) {
00152 if (in_array($role, $ACT['deny'], true)) { return false; }
00153 }
00154 return true;
00155 }
00156
00157 do {
00158 // 如果 allow 要求用户具有角色,但用户没有角色时直接不通过检查
00159 if ($ACT['allow'] == RBAC_HAS_ROLE) {
00160 if (!empty($roles)) { break; }
00161 return false;
00162 }
00163
00164 // 如果 allow 要求用户没有角色,但用户有角色时直接不通过检查
00165 if ($ACT['allow'] == RBAC_NO_ROLE) {
00166 if (empty($roles)) { break; }
00167 return false;
00168 }
00169
00170 if ($ACT['allow'] != RBAC_NULL) {
00171 // 如果 allow 要求用户具有特定角色,则进行检查
00172 $passed = false;
00173 foreach ($roles as $role) {
00174 if (in_array($role, $ACT['allow'], true)) {
00175 $passed = true;
00176 break;
00177 }
00178 }
00179 if (!$passed) { return false; }
00180 }
00181 } while (false);
00182
00183 // 如果 deny 没有设置,则检查通过
00184 if ($ACT['deny'] == RBAC_NULL) { return true; }
00185 // 如果 deny 为 RBAC_NO_ROLE,则只要用户具有角色就检查通过
00186 if ($ACT['deny'] == RBAC_NO_ROLE) {
00187 if (empty($roles)) { return false; }
00188 return true;
00189 }
00190 // 如果 deny 为 RBAC_HAS_ROLE,则只有用户没有角色信息时才检查通过
00191 if ($ACT['deny'] == RBAC_HAS_ROLE) {
00192 if (empty($roles)) { return true; }
00193 return false;
00194 }
00195 // 如果 deny 为 RBAC_EVERYONE,则检查失败
00196 if ($ACT['deny'] == RBAC_EVERYONE) {
00197 return false;
00198 }
00199
00200 // 只有 deny 中没有用户的角色信息,则检查通过
00201 foreach ($roles as $role) {
00202 if (in_array($role, $ACT['deny'], true)) { return false; }
00203 }
00204 return true;
00205 }
00206
00214 function prepareACT($ACT)
00215 {
00216 $ret = array();
00217 $arr = array('allow', 'deny');
00218 foreach ($arr as $key) {
00219 do {
00220 if (!isset($ACT[$key])) {
00221 $value = RBAC_NULL;
00222 break;
00223 }
00224
00225 if ($ACT[$key] == RBAC_EVERYONE || $ACT[$key] == RBAC_HAS_ROLE
00226 || $ACT[$key] == RBAC_NO_ROLE || $ACT[$key] == RBAC_NULL) {
00227 $value = $ACT[$key];
00228 break;
00229 }
00230
00231 $value = explode(',', strtoupper($ACT[$key]));
00232 $value = array_filter(array_map('trim', $value), 'trim');
00233 if (empty($value)) { $value = RBAC_NULL; }
00234 } while (false);
00235 $ret[$key] = $value;
00236 }
00237
00238 return $ret;
00239 }
00240 }
RBAC角色权限设置文件
array(
'allow' => '允许访问该控制器的角色名',
'deny' => '禁止访问该控制器的角色名',
'actions' => array(
'动作名' => array(
'allow' => '允许访问该动作的角色名',
'deny' => '禁止访问该动作的角色名',
),
// .... 更多动作
),
基于RBAC模型的通用权限管理系统的设计
(数据模型)的扩展
1 RBAC模型
访问控制是针对越权使用资源的防御措施。基本目标是为了限制访问主体(用户、进程、服务等)对访问客体(文件、系统等)的访问权限,从而使计算机系统在合法范围内使用;决定用户能做什么,也决定代表一定用户利益的程序能做什么[1]。
企业环境中的访问控制策略一般有三种:自主型访问控制方法、强制型访问控制方法和基于角色的访问控制方法(RBAC)。其中,自主式太弱,强制式太强,二者工作量大,不便于管理[1]。基于角色的访问控制方法是目前公认的解决大型企业的统一资源访问控制的有效方法。其显著的两大特征是:1.减小授权管理的复杂性,降低管理开销;2.灵活地支持企业的安全策略,并对企业的变化有很大的伸缩性。
NIST(The National Institute of Standards and Technology,美国国家标准与技术研究院)标准RBAC模型由4个部件模型组成,这4个部件模型分别是基本模型RBAC0(Core RBAC)、角色分级模型RBAC1(Hierarchal RBAC)、角色限制模型RBAC2(Constraint RBAC)和统一模型RBAC3(Combines RBAC)[1]。RBAC0模型如图1所示。
a. RBAC0定义了能构成一个RBAC控制系统的最小的元素集合。在RBAC之中,包含用户users(USERS)、角色roles(ROLES)、目标objects(OBS)、操作operations(OPS)、许可权permissions(PRMS)五个基本数据元素,权限被赋予角色,而不是用户,当一个角色被指定给一个用户时,此用户就拥有了该角色所包含的权限。会话sessions是用户与激活的角色集合之间的映射。RBAC0与传统访问控制的差别在于增加一层间接性带来了灵活性,RBAC1、RBAC2、RBAC3都是先后在RBAC0上的扩展。
b. RBAC1引入角色间的继承关系,角色间的继承关系可分为一般继承关系和受限继承关系。一般继承关系仅要求角色继承关系是一个绝对偏序关系,允许角色间的多继承。而受限继承关系则进一步要求角色继承关系是一个树结构。
c. RBAC2模型中添加了责任分离关系。RBAC2的约束规定了权限被赋予角色时,或角色被赋予用户时,以及当用户在某一时刻激活一个角色时所应遵循的强制性规则。责任分离包括静态责任分离和动态责任分离。约束与用户-角色-权限关系一起决定了RBAC2模型中用户的访问许可。
d. RBAC3包含了RBAC1和RBAC2,既提供了角色间的继承关系,又提供了责任分离关系。
2核心对象模型设计
根据RBAC模型的权限设计思想,建立权限管理系统的核心对象模型.对象模型中包含的基本元素主要有:用户(Users)、用户组(Group)、角色(Role)、目标(Objects)、访问模式(Access Mode)、操作(Operator)。主要的关系有:分配角色权限PA(Permission Assignment)、分配用户角色UA(Users Assignmen描述如下:
a .控制对象:是系统所要保护的资源(Resource),可以被访问的对象。资源的定义需要注意以下两个问题:
1.资源具有层次关系和包含关系。例如,网页是资源,网页上的按钮、文本框等对象也是资源,是网页节点的子节点,如可以访问按钮,则必须能够访问页面。
2.这里提及的资源概念是指资源的类别(Resource Class),不是某个特定资源的实例(Resource Instance)。资源的类别和资源的实例的区分,以及资源的粒度的细分,有利于确定权限管理系统和应用系统之间的管理边界,权限管理系统需要对于资源的类别进行权限管理,而应用系统需要对特定资源的实例进行权限管理。两者的区分主要是基于以下两点考虑:
一方面,资源实例的权限常具有资源的相关性。即根据资源实例和访问资源的主体之间的关联关系,才可能进行资源的实例权限判断。 例如,在管理信息系统中,需要按照营业区域划分不同部门的客户,A区和B区都具有修改客户资料这一受控的资源,这里“客户档案资料”是属于资源的类别的范畴。如果规定A区只能修改A区管理的客户资料,就必须要区分出资料的归属,这里的资源是属于资源实例的范畴。客户档案(资源)本身应该有其使用者的信息(客户资料可能就含有营业区域这一属性),才能区分特定资源的实例操作,可以修改属于自己管辖的信息内容。
另一方面,资源的实例权限常具有相当大的业务逻辑相关性。对不同的业务逻辑,常常意味着完全不同的权限判定原则和策略。
b.权限:对受保护的资源操作的访问许可(Access Permission),是绑定在特定的资源实例上的。对应地,访问策略(Access Strategy)和资源类别相关,不同的资源类别可能采用不同的访问模式(Access Mode)。例如,页面具有能打开、不能打开的访问模式,按钮具有可用、不可用的访问模式,文本编辑框具有可编辑、不可编辑的访问模式。同一资源的访问策略可能存在排斥和包含关系。例如,某个数据集的可修改访问模式就包含了可查询访问模式。
c.用户:是权限的拥有者或主体。用户和权限实现分离,通过授权管理进行绑定。
d.用户组:一组用户的集合。在业务逻辑的判断中,可以实现基于个人身份或组的身份进行判断。系统弱化了用户组的概念,主要实现用户(个人的身份)的方式。
e.角色:权限分配的单位与载体。角色通过继承关系支持分级的权限实现。例如,科长角色同时具有科长角色、科内不同业务人员角色。
f.操作:完成资源的类别和访问策略之间的绑定。
g.分配角色权限PA:实现操作和角色之间的关联关系映射。
h.分配用户角色UA:实现用户和角色之间的关联关系映射。
该对象模型最终将访问控制模型转化为访问矩阵形式。访问矩阵中的行对应于用户,列对应于操作,每个矩阵元素规定了相应的角色,对应于相应的目标被准予的访问许可、实施行为。按访问矩阵中的行看,是访问能力表CL(Access Capabilities)的内容;按访问矩阵中的列看,是访问控制表ACL(Access Control Lists)的内容。
数据模型图如下:
希望各位多提意见 ,再完善