Zend Framework1-Zend_Acl

来源:互联网 发布:娄烨电影知乎 编辑:程序博客网 时间:2024/06/05 15:27

1、简介

Zend_Acl为权限管理提供轻量并灵活的访问控制列表 (ACL:access control list) 的实现。一般地,应用软件可以利用这样的功能限制某些特定对象来访问特定保护的对象。

在本文档中,

  • resource (资源)是一个限制访问的对象。

  • role (角色)是一个可以发出请求去访问Resource的对象。

简单地说就是:roles 请求访问 resources。例如,如果停车服务员要进入汽车,那么,这个停车服务员就是发出请求的 role,而这辆汽车就是 resource,因为不是所有人都有权进入汽车。

通过规范和访问控制列表(ACL)的使用,应用软件可以控制角色(roles)如何来访问资源(resources)。

1.1关于资源(Resource)

在 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 分配影响全部或者特殊权限的规则到一个或多个资源。

1.2关于角色(Role)

象 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如何解决它。

Example #1 角色之间的多重继承

下面的代码定义了三个基本角色 - "guest", "member", 和 "admin" - 其它角色可以从它们继承。接着,创建一个"someUser" 角色并从其它三个基本角色继承。 在$parents 数组里这些角色出现的顺序很重要。 必要时,Zend_Acl 不但搜索为被查询的角色 (这里指 "someUser")定义的访问规则,而且搜索为被查询的角色所继承的角色 (这里指 "guest", "member", and "admin")定义的访问规则:

$acl = new Zend_Acl();$acl->addRole(new Zend_Acl_Role('guest'))    ->addRole(new Zend_Acl_Role('member'))    ->addRole(new Zend_Acl_Role('admin'));$parents = array('guest', 'member', 'admin');$acl->addRole(new Zend_Acl_Role('someUser'), $parents);$acl->add(new Zend_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"。

Note: 当为一个角色指定多重父(角色)时,请记住,对于一个授权查询的可用规则,最后列出的父(角色)是首先被搜索的

1.3创建访问控制列表(ACL)

ACL 可以表示任何一组物理或虚拟对象,然而,作为示范,我们将创建一个基本的内容管理系统的(CMS)ACL, 在一个范围很宽的多样化区域里,它将维护若干个等级的组。

为创建一个新的 ACL 对象,我们不带参数地实例化这个 ACL:

$acl = new Zend_Acl();

Note: 除非开发者指明一个"allow" 规则,Zend_Acl 禁止任何 role 对任何 resource 的任何访问权限。

1.4注册角色(Role)

CMS 通常需要一个分级的权限系统来决定它的用户的授权能力。

作为示范,“Guest”组允许有限的访问,“Staff”适合大多数的执行日常操作的CMS用户,“Editor”组适合于发布、复核、存档和删除内容, 最后“Administrator”组的任务包括所有其它组的内容并包括敏感的信息、用户管理、后台配置数据和备份/导出。 这

组许可可以被表示为一个 role 注册表,允许每个组从“”组继承权限,也可以为单一的组提供独特的权限。这些许可可以表示如下:

一个CMS范例的访问控制名称独特的许可从...继承的许可GuestViewN/AStaffEdit, Submit, ReviseGuestEditorPublish, Archive, DeleteStaffAdministrator(Granted all access)N/A

对于这个范例,Zend_Acl_Role 被使用,但任何实现Zend_Acl_Role_Interface 的对象是可接受的。这些组可以被添加到 role 注册表如下:

$acl = new Zend_Acl();// 用 Zend_Acl_Role 把组添加到 Role 注册表// Guest 不继承访问控制$roleGuest = new Zend_Acl_Role('guest');$acl->addRole($roleGuest);// Staff 从 guest 继承$acl->addRole(new Zend_Acl_Role('staff'), $roleGuest);/*另外, 上面的也可这样来写:$acl->addRole(new Zend_Acl_Role('staff'), 'guest');*/// Editor 从 staff 继承$acl->addRole(new Zend_Acl_Role('editor'), 'staff');// Administrator 不继承访问控制$acl->addRole(new Zend_Acl_Role('administrator'));

1.5定义访问控制

现在 ACL包含了相关的 roles,可以建立规则来定义 roles 如何访问 resources。

你也许注意到我们在这个范例里没有定义任何特定的 resources, 这简单地表示这些规则适用于所有 resources。

Zend_Acl 提供一个实现,籍此,规则只需要被分配从一般到特殊,最小化规则的需求,因为 resources 和 roles 继承由它们祖先定义的规则。

Note: 一般来说,当且仅当更具体的规则没有使用, Zend_Acl 就服从已给定的规则。

因此,我们可以用最少量的代码来定义适度复杂的规则。应用上面定义的基本许可:

$acl = new Zend_Acl();$roleGuest = new Zend_Acl_Role('guest');$acl->addRole($roleGuest);$acl->addRole(new Zend_Acl_Role('staff'), $roleGuest);$acl->addRole(new Zend_Acl_Role('editor'), 'staff');$acl->addRole(new Zend_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。

1.6查询 ACL

我们现在有一个灵活的 ACL 可以用来决定请求者在整个 web 应用里是否拥有执行功能的许可。用isAllowed()方法来执行查询相当简单:

echo $acl->isAllowed('guest', null, 'view') ? "allowed" : "denied";// allowedecho $acl->isAllowed('staff', null, 'publish') ? "allowed" : "denied";// deniedecho $acl->isAllowed('staff', null, 'revise') ? "allowed" : "denied";// allowedecho $acl->isAllowed('editor', null, 'view') ? "allowed" : "denied";// allowed because of inheritance from guestecho $acl->isAllowed('editor', null, 'update') ? "allowed" : "denied";// denied because no allow rule for 'update'echo $acl->isAllowed('administrator', null, 'view') ? "allowed" : "denied";// allowed because administrator is allowed all privilegesecho $acl->isAllowed('administrator') ? "allowed" : "denied";// allowed because administrator is allowed all privilegesecho $acl->isAllowed('administrator', null, 'update') ? "allowed" : "denied";// allowed because administrator is allowed all privileges



2、精细的访问控制

前面定义的基本的 ACL 显示如何在整个 ACL (所有的 resources )允许各种各样的权限。

然而在实践中,访问控制趋向于拥有例外和可变程度的复杂性。Zend_Acl 允许你直截了当并灵活地完成这些精细准确的控制。

对于CMS范例,'staff' 组覆盖了绝大多数用户的需求,同时,一个新的 'marketing' 组要求在CMS中访问时事通讯和最近的新闻。这个组相当地自给自足并有能力发布和归档时事通讯和最近的新闻。

另外,还要求 'staff' 组被允许浏览新闻故事但不能修订最近的新闻。最后,不可能让每一个人(包括系统管理员)去归档任何'announcement' 新闻故事,因为它们只有1-2天的生命周期。

首先我们修订 role 注册表来反映这些变化。我们已经确定 'marketing' 组和 'staff' 组有着同样的基本许可,所以我们用从 'staff' 组继承许可的方法来定义 'marketing' 组

// 新 marketing 组从 staff 组继承许可$acl->addRole(new Zend_Acl_Role('marketing'), 'staff');  

然后, 注意上面的访问控制涉及到特定的 resources ( 例如 "newsletter", "latest news", "announcement news"). 现在我们来添加这些 resources:

// Create Resources for the rules// newsletter$acl->add(new Zend_Acl_Resource('newsletter'));// news$acl->add(new Zend_Acl_Resource('news'));// latest news$acl->add(new Zend_Acl_Resource('latest'), 'news');// announcement news$acl->add(new Zend_Acl_Resource('announcement'), 'news');        

接着,这个是在 ACL 的目标区域定义更特定的规则的概况:

// Marketing must be able to publish and archive newsletters and the latest news$acl->allow('marketing', array('newsletter', 'latest'),  array('publish', 'archive'));// Staff (和 marketing, 通过继承), 禁止修订 latest news$acl->deny('staff', 'latest', 'revise');// Everyone (包括 administrators) 禁止归档 news announcements$acl->deny(null, 'announcement', 'archive');        

我们现在能够查询到 ACL 的最新变化:

echo $acl->isAllowed('staff', 'newsletter', 'publish') ? "allowed" : "denied";// deniedecho $acl->isAllowed('marketing', 'newsletter', 'publish') ? "allowed" : "denied";// allowedecho $acl->isAllowed('staff', 'latest', 'publish') ? "allowed" : "denied";// deniedecho $acl->isAllowed('marketing', 'latest', 'publish') ? "allowed" : "denied";// allowedecho $acl->isAllowed('marketing', 'latest', 'archive') ? "allowed" : "denied";// allowedecho $acl->isAllowed('marketing', 'latest', 'revise') ? "allowed" : "denied";// deniedecho $acl->isAllowed('editor', 'announcement', 'archive') ? "allowed" : "denied";// deniedecho $acl->isAllowed('administrator', 'announcement', 'archive') ? "allowed" : "denied";// denied

2.1除去访问控制

要从 ACL 中除去一个或多个访问规则,只要简单地用 removeAllow()removeDeny() 方法即可。

如果提供一个null参数值给allow()deny() 方法,则访问规则将应用到所有的角色,资源和/或权限上。

// 除去 “禁止 staff 修订最近的新闻”(和marketing, 由于继承的原因)//(等于允许staff修订最近的新闻 Jason注)$acl->removeDeny('staff', 'latest', 'revise');echo $acl->isAllowed('marketing', 'latest', 'revise') ? "allowed" : "denied";// allowed// Remove the allowance of publishing and archiving newsletters to marketing$acl->removeAllow('marketing', 'newsletter', array('publish', 'archive'));echo $acl->isAllowed('marketing', 'newsletter', 'publish') ? "allowed" : "denied";// deniedecho $acl->isAllowed('marketing', 'newsletter', 'archive') ? "allowed" : "denied";// denied

如上所示,对于权限的修改可能是增量的,但使用 null 值(未指定权限项的参数值)超越了对权限的增量修改。

(所谓的增量修改是指可以对 Resources 一个一个地添加权限或禁止,而如果未指定权限参数值,即使用null 值,可以使得这些步骤简化,一次性地对某个Resource的所有权限进行允许或禁止。Jason注,Haohappy补)

// 允许 marketing 对 latest news 有所有的许可$acl->allow('marketing', 'latest');echo $acl->isAllowed('marketing', 'latest', 'publish') ? "allowed" : "denied";// allowedecho $acl->isAllowed('marketing', 'latest', 'archive') ? "allowed" : "denied";// allowedecho $acl->isAllowed('marketing', 'latest', 'anything') ? "allowed" : "denied";// allowed

3、高级用法

3.1保存 ACL 数据确保持久性

Zend_Acl 就是这样设计的,它不需要为 ACL 数据的存储而要求任何特别的后台技术如数据库或者缓冲服务器。

它是完全的 PHP 实现使得在Zend_Acl之上构建定制的管理工具相当地容易和灵活。许多情形需要一些 ACL 的交互式维护,并且 Zend_Acl 为设定、查询、应用软件的访问控制提供了方法。

因为期望应用案例有多种变化来适应不同的情形,ACL 数据的存储因此留给开发者来完成。

因为 Zend_Acl 是可序列化的,所以 ACL 的对象可以用 PHP 中的 » serialize() 函数来序列化,并且结果可以存在开发者所期望的任何地方,例如文件、数据库、或缓存机构。

3.2使用声明(Assert)来编写条件性的 ACL 规则

有时候允许或禁止一个 Role 访问一个 Resource 的规则不是绝对的而是依靠不同的标准。

例如,只有在 8:00am 和 5:00pm 之间,特定的访问才被允许。另外一个禁止访问的例子是因为一个请求来自于被标记为不良的 IP 地址。

Zend_Acl 对基于无论开发者有什么需要的条件的规则实现有个内置的支持。

Zend_Acl 用 Zend_Acl_Assert_Interface 提供支持有条件的规则。为了使用规则声明接口,开发者写了一个实现接口中assert() 方法的类。

class CleanIPAssertion implements Zend_Acl_Assert_Interface{    public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $role = null, Zend_Acl_Resource_Interface $resource = null, $privilege = null)    {        return $this->_isCleanIP($_SERVER['REMOTE_ADDR']);    }    protected function _isCleanIP($ip)    {        // ...    }}}

一旦声明类可用,当分配有条件的规则时,开发者必需提供声明类的一个实例。用声明建立的规则只适用于当声明方法返回 true。

$acl = new Zend_Acl();$acl->allow(null, null, null, new CleanIPAssertion());        

上面的代码创建了一个有条件的 allow 规则,它允许所有人对所有资源有所有的访问权限,除非请求的 IP 列在“黑名单”上。

如果一个请求来自于一个不是“清白”的 IP,这个 allow 规则就不适用。因此这个规则适用于所有的 Roles、所有的 Resources 和所有的权限,一个“不清白”的 IP 将导致一个禁止访问。

这是一个特例,对于其它所有案例(例如,一个特定的 Role、Resource、或者被指定规则的权限),一个失败的声明将导致规则不适用,并且其它规则将被用于决定访问是被允许或禁止。

为了给声明类提供一个上下文环境(Context)来决定所需的条件,Assert对象的 assert() 方法将以ACL、 Role、 Resource 和适用于授权查询(例如isAllowed())的权限作为参数。



4、实例详解

4.1Zend_Acl的误解

很多人会误认为ACL的resource和privilege是controller和action,这是错误的。

对Zend_Acl而言,resource可以是任何事物-一个controller,一个file,一个module...

privilege就像resource一样,也可以是任何与resource相关的事物,例如,如果resource是一个controler,那privilege就可以是一个action,或者如果controller是一个file或者model,那么它就可能是read或者write。

4.2创建一个简单的ACL

前面提到,Zend_Acl是由资源(resources)、权限(privileges)和角色(roles)构成的。privileges是访问resources的权限级别。

roles就是以指定的访问权限(privilege)来访问resource的访问对象,可以是一个用户,用户组or anything you wish to associate such data with。

下面是一个ACL的简单代码,只包含了少量的resources和roles

class My_Acl extends Zend_Acl { 2   public function __construct() { 3     //Add a new role called "guest" 4     $this->addRole(new Zend_Acl_Role('guest')); 5   6     //Add a role called user, which inherits from guest 7     $this->addRole(new Zend_Acl_Role('user'), 'guest'); 8   9     //Add a resource called page10     $this->add(new Zend_Acl_Resource('page'));11  12     //Add a resource called news, which inherits page13     $this->add(new Zend_Acl_Resource('news'), 'page');14  15     //Finally, we want to allow guests to view pages16     $this->allow('guest', 'page', 'view');17  18     //and users can comment news19     $this->allow('user', 'news', 'comment');20   }21 }

此时创建一个My_Acl的实例,就可以对应一些简单的权限对应设定,但是具体那个角色拥有什么权限呢?

guest角色对于page资源拥有一个view的权限,之后user角色继承了guest,所以user也拥有了page的view权限。

news资源继承了page,所以所有用户对news资源都拥有与对page资源相同的权限。

在构造函数的最后一行,我们为user角色在news资源上新增了一个comment的权限。而此时只有user角色拥有news资源的comment权限,guest没有。

4.3使用ACL

使用一个ACL类非常简单。你只需要调用isAllowed方法,同时向它传递一个role,resource和一个privilege参数既可。

而最大的难点在于你如何确定哪个是role, resource和privilege。

该如何确定哪个是角色?一个非常简单的假设就是:如果这有一个登陆用户user,那么这个角色就是user,否则就是guest。

那么资源呢?这取决于你的应用程序,这可以从你所包含的文件名中猜测出来。例如:对于上面的例子中,我们有一个page.php文件和一个news.php文件用来对应于page资源,而news对应于news资源。

最后什么是权限呢?当我们简单的读取page的时候,你可以使用view权限。稍后代码会检测一些其它一些权限规则,例如comment权限以及显示comment box的权限等。

下面是一个简单的实例

1 $role = 'guest';2 if(isset($_SESSION['auth']))3 $role = 'user';4 5 $acl = new My_Acl();6 7 if($acl->isAllowed($role, 'news', 'comment')) {8 //Some code here to display a news box9 }
4.4在ZF项目中使用ACL
在zf项目中,资源和权限通常会在请求资源中确定。通常我们可以创建一个检测权限的插件:
 1 class My_Plugin_Acl extends Zend_Controller_Plugin_Abstract { 2   private $_acl = null; 3   4   public function __construct(Zend_Acl $acl) { 5     $this->_acl = $acl; 6   } 7   8   public function preDispatch(Zend_Controller_Request_Abstract $request) { 9     //As in the earlier example, authed users will have the role user10     $role = (Zend_Auth::getInstance()->hasIdentity())11           ? 'user'12           : 'guest';13  14     //For this example, we will use the controller as the resource:15     $resource = $request->getControllerName();16  17     if(!$this->_acl->isAllowed($role, $resource, 'view')) {18       //If the user has no access we send him elsewhere by changing the request19       $request->setModuleName('auth')20               ->setControllerName('auth')21               ->setActionName('login');22     }23   }24 }
我们可以在上面的例子中创建一个静态方法来代替构造函数。将上面的插件加载到前段控制器中:

1 $acl = new My_Acl();2 3 //assuming $fc is the front controller4 $fc->registerPlugin(new My_Plugin_Acl($acl));
此时每个请求都会核对权限控制列表,并且没有view权限的角色将会别拒绝访问。

接下来就是关于如何检测comment权限

在需要检测comment权限的controller中添加一些代码来向view中传递一个boolean值来确定权限。

1 public function someAction() {2 $role = (Zend_Auth::getInstance()->hasIdentity())3 ? 'user'4 : 'guest';5 6 //assuming $this->_acl contains the acl7 $this->view->canComment = $this->_acl->isAllowed($role, 'news', 'comment');8 }
4.5创建自定义角色类
通过扩展Zend_Acl_Role_Interface接口来定义角色类,使其具有更多的灵活性。该接口只包含一个方法getRoleId()

 1 class Common_Module_acl_user implements Zend_Acl_Role_Interface 2 { 3     protected $_name; 4      5     public function __construct($name) 6     { 7         $this->_name = $name; 8     } 9     10     public function getRoleId()11     {12         return 'user_'. $this->_name;13     }14 }
此时只需将该类的实例传递给Zend_Acl对象既可

1 $acl = new Zend_Acl();2 //添加一个名为user_zh的角色3 $acl->addRole(new Common_Module_acl_user('zh'));
4.6创建自定义资源类
通过扩展Zend_Acl_Resource_Interface接口来实现自定义资源类

1 class Resource_Controller implements Zend_Acl_Resource_Interface {2     public function __construct($id) {3       $this->_id = $id;4     }5  6     public function getResourceId() {7       return 'controller-' . $this->_id; 8     }9 }
实现方法同角色类一样

0 0