2.3.2.使用声明(Assert)来编写条件性的ACL规则
Zend_Acl为权限管理提供轻量并灵活的访问控制列表(ACL,accesscontrollist)的实现。一般地,应用软件可以利用这样的功能限制某些特定对象来访问特定保护的对象。
在本文档中,
简单地说就是:roles请求访问resources。例如,如果停车服务员要进入汽车,那么,这个停车服务员就是发出请求的role,而这辆汽车就是resource,因为不是所有人都有权进入汽车。
通过规范和访问控制列表(ACL)的使用,应用软件可以控制角色(roles)如何来访问资源(resources)。
在Zend_Acl中,创建一个resource非常简单。Zend_Acl提供了resource接口Zend_Acl_Resource_Interface使开发者在程序中创建resources非常容易。为了使Zend_Acl把某个对象当作一个resource,一个类只需要实现这个只包含了一个方法getResourceId()的接口。另外,Zend_Acl_Resource是一个包含在Zend_Acl里作为一个基本的resource实现的类,开发者可以任意扩展它。
Zend_Acl提供了一个树结构,它可以添加多个resources(或者叫“访问控制下的区域”)。因为resources被存储在这样一个树结构,所以它们可以被组织成从一般(树根)到特殊(树叶)。基于特殊resource的查询将自动从resource的等级结构中搜索分配给祖先rResources的规则,它允许简单的规则继承。例如,要把一个缺省的规则应用到一个城市的每个建筑物,就简单地把这个规则分配给这个城市,而不是把规则分配给每个建筑物。然而,有些建筑物也许要求例外的规则,在Zend_Acl里,很容易地通过分配例外规则给每个有这样要求的建筑物来实现。一个resource可以从唯一的一个父resource继承,而这个父resource也有它自己的父resource,等等。
Zend_Acl也支持基于resources的权限(例如:"create","read","update","delete"),所以开发者可以根据resource分配影响全部或者特殊权限的规则到一个或多个资源。
象Resources一样,创建一个role也非常简单。Zend_Acl提供了Zend_Acl_Role_Interface使开发者创建roles非常容易。为了使Zend_Acl把某个对象当作一个role,一个类只需要实现这个只包含了一个方法getRoleId()的接口。另外,Zend_Acl_Role是一个包含在Zend_Acl里作为一个基本的role实现的类,开发者可以任意扩展它。
在Zend_Acl,一个role可以从一个或多个role继承,这就是在role中支持规则的继承。例如,一个用户role,如“sally”,可以属于一个或多个role,如:“editor”和“administrator”。开发者可以分别给“editor”和“administrator”分配规则,而“sally”将从它们俩继承规则,不需要直接给“sally”分配规则。
虽然从多重角色继承的能力非常有用,但是多重继承也带来了一定程度的复杂性。下面的例子来示例含糊情形和Zend_Acl如何解决它。
下面的代码定义了三个基本角色-"guest","member",和"admin"-其它角色可以从它们继承。接着,创建一个"someUser"角色并从其它三个基本角色继承。在$parents数组里这些角色出现的顺序很重要。必要时,Zend_Acl不但搜索为被查询的角色(这里指"someUser")定义的访问规则,而且搜索为被查询的角色所继承的角色(这里指"guest","member",and"admin")定义的访问规则:
$acl=newZend_Acl();
//添加角色
//newZend_Acl_Role('guest')新角色
$acl->addRole(newZend_Acl_Role('guest'))
->addRole(newZend_Acl_Role('member'))
->addRole(newZend_Acl_Role('admin'));
$parents=array('guest','member','admin');
//继承自数组角色
$acl->addRole(newZend_Acl_Role('someUser'),$parents);
//添加资源
$acl->add(newZend_Acl_Resource('someResource'));
//分配资源
$acl->deny('guest','someResource');
$acl->allow('member','someResource');
echo$acl->isAllowed('someUser','someResource')?'allowed':'denied';
因为没有明确地为"someUser"和"someResource"定义规则,所以Zend_Acl必须搜索可能为"someUser"所继承的角色定义的规则。首先,"admin"角色被访问,没有给它定义访问规则。接着,"member"角色被访问,Zend_Acl发现有个规则允许"member"访问"someResource"。
如果Zend_Acl打算继续检查为父角色所定义的规则,然而,它将发现"guest"被拒绝访问"someResource"。这带来了模棱两可的情形,"someUser"即被允许又被禁止访问"someResource",因为它从不同的父角色继承了冲突的规则。
Zend_Acl解决了这个模棱两可的问题,当它发现第一个可用的规则时,就完成了查询。对于这个案例,因为"member"角色先于"guest"角色被检查,这个例子将打印"allowed"。
|
注意
|
当为一个角色指定多重父(角色)时,请记住,对于一个授权查询的可用规则,最后列出的父(角色)是首先被搜索的。
|
ACL可以表示任何一组物理或虚拟对象,然而,作为示范,我们将创建一个基本的内容管理系统的(CMS)ACL,在一个范围很宽的多样化区域里,它将维护若干个等级的组。为创建一个新的ACL对象,我们不带参数地实例化这个ACL:
$acl=newZend_Acl();
|
注意
|
除非开发者指明一个"allow"规则,Zend_Acl禁止任何role对任何resource的任何访问权限。
|
CMS通常需要一个分级的权限系统来决定它的用户的授权能力。作为示范,“Guest”组允许有限的访问,“Staff”适合大多数的执行日常操作的CMS用户,“Editor”组适合于发布、复核、存档和删除内容,最后“Administrator”组的任务包括所有其它组的内容并包括敏感的信息、用户管理、后台配置数据和备份/导出。这组许可可以被表示为一个role注册表,允许每个组从“”组继承权限,也可以为单一的组提供独特的权限。这些许可可以表示如下:
名称
|
独特的许可
|
从...继承的许可
|
Guest
|
View
|
N/A
|
Staff
|
Edit,Submit,Revise
|
Guest
|
Editor
|
Publish,Archive,Delete
|
Staff
|
Administrator
|
(Grantedallaccess)
|
N/A
|
对于这个范例,Zend_Acl_Role被使用,但任何实现Zend_Acl_Role_Interface的对象是可接受的。这些组可以被添加到role注册表如下:
$acl=newZend_Acl();
//用Zend_Acl_Role把组添加到Role注册表
//Guest不继承访问控制
$roleGuest=newZend_Acl_Role('guest');
$acl->addRole($roleGuest);
//Staff从guest继承
$acl->addRole(newZend_Acl_Role('staff'),$roleGuest);
/*
另外,上面的也可这样来写:
$acl->addRole(newZend_Acl_Role('staff'),'guest');
*/
//Editor从staff继承
$acl->addRole(newZend_Acl_Role('editor'),'staff');
//Administrator不继承访问控制
$acl->addRole(newZend_Acl_Role('administrator'));
现在ACL包含了相关的roles,可以建立规则来定义roles如何访问resources。你也许注意到我们在这个范例里没有定义任何特定的resources,这简单地表示这些规则适用于所有resources。Zend_Acl提供一个实现,籍此,规则只需要被分配从一般到特殊,最小化规则的需求,因为resources和roles继承由它们祖先定义的规则。
|
注意
|
一般来说,当且仅当更具体的规则没有使用,Zend_Acl就服从已给定的规则。
|
因此,我们可以用最少量的代码来定义适度复杂的规则。应用上面定义的基本许可:
$acl=newZend_Acl();
$roleGuest=newZend_Acl_Role('guest');
$acl->addRole($roleGuest);
$acl->addRole(newZend_Acl_Role('staff'),$roleGuest);
$acl->addRole(newZend_Acl_Role('editor'),'staff');
$acl->addRole(newZend_Acl_Role('administrator'));
//Guest只可以浏览内容
$acl->allow($roleGuest,null,'view');
/*
另外,上面也可写为:
$acl->allow('guest',null,'view');
*/
//Staff从guest继承浏览权限,但也要另外的权限
//分配权限
$acl->allow('staff',null,array('edit','submit','revise'));
//Editor从Staff继承view,edit,submit和revise权限
//但也要另外的权限
$acl->allow('editor',null,array('publish','archive','delete'));
//Administrator不需要继承任何权限,它拥有所有的权限
$acl->allow('administrator');
在上面allow()中调用中null的值用来表明allow规则适用于所有的resources。
我们现在有一个灵活的ACL可以用来决定请求者在整个web应用里是否拥有执行功能的许可。用isAllowed()方法来执行查询相当简单:
echo$acl->isAllowed('guest',null,'view')?
"allowed":"denied";
//allowed
echo$acl->isAllowed('staff',null,'publish')?
"allowed":"denied";
//denied
echo$acl->isAllowed('staff',null,'revise')?
"allowed":"denied";
//allowed
echo$acl->isAllowed('editor',null,'view')?
"allowed":"denied";
//allowedbecauseofinheritancefromguest
echo$acl->isAllowed('editor',null,'update')?
"allowed":"denied";
//deniedbecausenoallowrulefor'update'
echo$acl->isAllowed('administrator',null,'view')?
"allowed":"denied";
//allowedbecauseadministratorisallowedallprivileges
echo$acl->isAllowed('administrator')?
"allowed":"denied";
//allowedbecauseadministratorisallowedallprivileges
echo$acl->isAllowed('administrator',null,'update')?
"allowed":"denied";
//allowedbecauseadministratorisallowedallprivileges
在前一章节中定义的基本的ACL显示如何在整个ACL(所有的resources)允许各种各样的权限。然而在实践中,访问控制趋向于拥有例外和可变程度的复杂性。Zend_Acl允许你直截了当并灵活地完成这些精细准确的控制。
对于CMS范例,'staff'组覆盖了绝大多数用户的需求,同时,一个新的'marketing'组要求在CMS中访问时事通讯和最近的新闻。这个组相当地自给自足并有能力发布和归档时事通讯和最近的新闻。
另外,还要求'staff'组被允许浏览新闻故事但不能修订最近的新闻。最后,不可能让每一个人(包括系统管理员)去归档任何'announcement'新闻故事,因为它们只有1-2天的生命周期。
首先我们修订role注册表来反映这些变化。我们已经确定'marketing'组和'staff'组有着同样的基本许可,所以我们用从'staff'组继承许可的方法来定义'marketing'组
//新marketing组从staff组继承许可
$acl->addRole(newZend_Acl_Role('marketing'),'staff');
然后,注意上面的访问控制涉及到特定的resources(例如"newsletter","latestnews","announcementnews").现在我们来添加这些resources:
//CreateResourcesfortherules
//newsletter
$acl->add(newZend_Acl_Resource('newsletter'));
//news
$acl->add(newZend_Acl_Resource('news'));
//latestnews//资源继承
$acl->add(newZend_Acl_Resource('latest'),'news');
//announcementnews
$acl->add(newZend_Acl_Resource('announcement'),'news');
接着,这个是在ACL的目标区域定义更特定的规则的概况:
//Marketingmustbeabletopublishandarchivenewslettersandthe
//latestnews
$acl->allow('marketing',
array('newsletter','latest'),
array('publish','archive'));
//Staff(和marketing,通过继承),禁止修订latestnews
$acl->deny('staff','latest','revise');
//Everyone(包括administrators)禁止归档newsannouncements
$acl->deny(null,'announcement','archive');
我们现在能够查询到ACL的最新变化:
echo$acl->isAllowed('staff','newsletter','publish')?
"allowed":"denied";
//denied
echo$acl->isAllowed('marketing','newsletter','publish')?
"allowed":"denied";
//allowed
echo$acl->isAllowed('staff','latest','publish')?
"allowed":"denied";
//denied
echo$acl->isAllowed('marketing','latest','publish')?
"allowed":"denied";
//allowed
echo$acl->isAllowed('marketing','latest','archive')?
"allowed":"denied";
//allowed
echo$acl->isAllowed('marketing','latest','revise')?
"allowed":"denied";
//denied
echo$acl->isAllowed('editor','announcement','archive')?
"allowed":"denied";
//denied
echo$acl->isAllowed('administrator','announcement','archive')?
"allowed":"denied";
//denied
要从ACL中除去一个或多个访问规则,只要简单地用removeAllow()或removeDeny()方法即可。如果提供一个null参数值给allow()和deny()方法,则访问规则将应用到所有的角色,资源和/或权限上。
//除去“禁止staff修订最近的新闻”(和marketing,由于继承的原因)
//(等于允许staff修订最近的新闻Jason注)
$acl->removeDeny('staff','latest','revise');
echo$acl->isAllowed('marketing','latest','revise')?
"allowed":"denied";
//allowed
//Removetheallowanceofpublishingandarchivingnewslettersto
//marketing
$acl->removeAllow('marketing',
'newsletter',
array('publish','archive'));
echo$acl->isAllowed('marketing','newsletter','publish')?
"allowed":"denied";
//denied
echo$acl->isAllowed('marketing','newsletter','archive')?
"allowed":"denied";
//denied
如上所示,对于权限的修改可能是增量的,但使用null值(未指定权限项的参数值)超越了对权限的增量修改。(所谓的增量修改是指可以对Resources一个一个地添加权限或禁止,而如果未指定权限参数值,即使用null值,可以使得这些步骤简化,一次性地对某个Resource的所有权限进行允许或禁止。Jason注,Haohappy补)
//允许marketing对latestnews有所有的许可
$acl->allow('marketing','latest');
echo$acl->isAllowed('marketing','latest','publish')?
"allowed":"denied";
//allowed
echo$acl->isAllowed('marketing','latest','archive')?
"allowed":"denied";
//allowed
echo$acl->isAllowed('marketing','latest','anything')?
"allowed":"denied";
//allowed
Zend_Acl就是这样设计的,它不需要为ACL数据的存储而要求任何特别的后台技术如数据库或者缓冲服务器。它的完全的PHP实现使得在Zend_Acl之上构建定制的管理工具相当地容易和灵活。许多情形需要一些ACL的交互式维护,并且Zend_Acl为设定、查询、应用软件的访问控制提供了方法。
因为期望应用案例有多种变化来适应不同的情形,ACL数据的存储因此留给开发者来完成。因为Zend_Acl是可序列化的,所以ACL的对象可以用PHP中的serialize()函数来序列化,并且结果可以存在开发者所期望的任何地方,例如文件、数据库、或缓存机构。
有时候允许或禁止一个Role访问一个Resource的规则不是绝对的而是依靠不同的标准。例如,只有在8:00am和5:00pm之间,特定的访问才被允许。另外一个禁止访问的例子是因为一个请求来自于被标记为不良的IP地址。Zend_Acl对基于无论开发者有什么需要的条件的规则实现有个内置的支持。
Zend_Acl用Zend_Acl_Assert_Interface提供支持有条件的规则。为了使用规则声明接口,开发者写了一个实现接口中assert()方法的类。
classCleanIPAssertionimplementsZend_Acl_Assert_Interface
{
publicfunctionassert(Zend_Acl$acl,
Zend_Acl_Role_Interface$role=null,
Zend_Acl_Resource_Interface$resource=null,
$privilege=null)
{
return$this->_isCleanIP($_SERVER['REMOTE_ADDR']);
}
protectedfunction_isCleanIP($ip)
{
//...
}
}}
一旦声明类可用,当分配有条件的规则时,开发者必需提供声明类的一个实例。用声明建立的规则只适用于当声明方法返回true。
$acl=newZend_Acl();
$acl->allow(null,null,null,newCleanIPAssertion());
上面的代码创建了一个有条件的allow规则,它允许所有人对所有资源有所有的访问权限,除非请求的IP列在“黑名单”上。如果一个请求来自于一个不是“清白”的IP,这个allow规则就不适用。因此这个规则适用于所有的Roles、所有的Resources和所有的权限,一个“不清白”的IP将导致一个禁止访问。这是一个特例,对于其它所有案例(例如,一个特定的Role、Resource、或者被指定规则的权限),一个失败的声明将导致规则不适用,并且其它规则将被用于决定访问是被允许或禁止。
为了给声明类提供一个上下文环境(Context)来决定所需的条件,Assert对象的assert()方法将以ACL、Role、Resource和适用于授权查询(例如isAllowed())的权限作为参数。
document.body.oncopy = function() {
if (window.clipboardData) {
setTimeout(function() {
var text = clipboardData.getData("text");
if (text && text.length>300) {
text = text + "\r\n\n本文来自CSDN博客,转载请标明出处:" + location.href;
clipboardData.setData("text", text);
}
}, 100);
}
}
function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}
ZendFramework作为PHP的一个官方编程框架,刚一推出来就引起了震动,大家把它简称为“ZF”。看来PHP已经在寻求向企业开发市场进军。但是由于推出时间短,熟悉ZF的开发人员,非凡是国内的程序员,对ZF了解的还不多。现在市面上还找不到一本专门介绍ZF的图书,现有的资料,大部分是英文版,Zend公司与中国公司合作翻译的ZendFrameWork中文手册,还是个烂尾工程,而且是1.0或1.2版本的,最新的ZF
有人说,ZF简直就是ROR的翻版,看来ZF应该具有当前流行的动态语言的优点,非凡是对MVC的良好支持,
了解ZF的人,应该对ZF的权限治理印象深刻。ZF手册在第一章的简介之后,第二章就拉出Zend_Acl(访问控制授权)来介绍,而第三章就是Zend_Auth的内容(认证)。从知识认知的顺序上,我个人感觉这样安排的不合适的。事实上第二和第三章的内容介绍,牵扯了众多后续章节的内容,我们不得不从第7章Zend_Controller开始看起。
但是不得不承认ZF的认证和授权是个优秀的设计。ZF的认证相对简单,我们可以直接连接数据库表,对用户输入的用户名和密码进行比较,确认当前用户的身份。还提供了摘要式认证等方法。而ACL(访问控制列表)提供的授权就很强大。
ACL提出了资源、角色、动作(访问)三个概念,用这些概念可以轻易构造一个强大的权限治理系统。而且权限治理可以细化到模块下的浏览、添加、删除、修改等细微动作。(ASP.NET只能控制到页面(模块)级别,更细致的控制需要程序员自己设计和实现)。这里粘一些代码,仅仅是说明一下(在IIS6.0ZF1.51Apache2.2下调试的):
//4种角色:admin,editor,guest,auditor,poweruser
//4种资源:channel,item,bbs,blog
//3种动作:view,edit,revise
//要求:
//admin对所有资源有所有权限
//guest对所有资源只有view权限
//editor对channel,item,bbs有view,edit权限
//auditor对channel,item,bbs,blog有revise权限
//poweruser继续editor,auditor权限
//定义角色
$acl=newZend_Acl();
$roleAdmin=newZend_Acl_Role('admin');
$acl->addRole($roleAdmin);
$roleEditor=newZend_Acl_Role('editor');
$acl->addRole($roleEditor);
$roleGuest=newZend_Acl_Role('guest');
$acl->addRole($roleGuest);
$roleAuditor=newZend_Acl_Role('auditor');
$acl->addRole($roleAuditor);
$rolePoweruser=newZend_Acl_Role('poweruser');
$acl->addRole($rolePoweruser,array('editor','auditor'));
//添加资源
$acl->add(newZend_Acl_Resource('channel'));
$acl->add(newZend_Acl_Resource('item'));
$acl->add(newZend_Acl_Resource('bbs'));
$acl->add(newZend_Acl_Resource('blog'));
//分配权限
//admin:
$acl->allow($roleAdmin,null,null);
//editor:
$acl->allow('editor',array('channel','item','bbs'),array('view','edit'));
//guest:
$acl->allow('guest',null,'view');
//auditor:
$acl->allow('auditor',array('channel','item','bbs','blog'),array('revise'));
//poweruser(从editor,auditor继续权限),可以再叠加另外的权限
//$acl->allow('poweruser',null,null);
//给所有角色分配权限(包括admin)
//$acl->allow(null,'','');
//$acl->deny(null,'','');
查看以上代码的最终权限,可以参考一下代码:
//查看角色
foreach(array('admin','editor','guest','auditor','poweruser')as$role)
{
echo'<br>'.'角色'.$role.'<br>';
echo'<tableborder=1>';
echo'<tr><tdalign=center>\</td><th>channel</th><th>item</th><th>bbs</th><th>blog</th></tr>';
foreach(array('view','edit','revise')as$right)
{
echo'<tr>';
echo'<th>'.$right.'</th>';
foreach(array('channel','item','bbs','blog')as$resource)
{
echo'<td>';
echo$acl->isAllowed($role,$resource,$right)?"allowed":"denied";
echo'</td>';
}
echo'</tr>';
}
echo'</table>';
}
}
这些资源、角色、动作,在以上代码片段里,是作为硬编码存在的,实际项目中,可以作为数据在数据库里保存。灵活应用就需要程序员的聪明了。
ZF的用户,只划分到“角色”这一级,也就是说,不能给某个用户直接分配权限。这肯定是出于简化设计而考虑的,否则设计和实现会复杂的多!这个有个小缺点,就是要为一个用户分配权限,必须首先建一个角色(相当于用户组),再把该用户加入到该角色下,通过角色来给该用户分配权限。而权限设计中,用户、角色、资源、动作的相互包含、交叉,其结果导致问题变得非常复杂!这么复杂的逻辑关系,不借助其他工具(例如计算机),人的大脑大多数情况下是很难想明白的。
本文永久链接: http://www.zzxj.net/blog/fxs_2008/archive/2009/11/15/85.html
发表于 @ 2009年11月15日 |评论(loading... )|收藏