如果您正在构建 CMS,您可能需要具有不同权限级别的不同用户角色(超级用户、管理员、用户)。代码太复杂?输入 CakePHP 的 ACL(访问控制列表)。通过正确的设置,您只需一行即可检查用户权限。
ACL 允许您创建具有各自角色的用户层次结构。这是一个简单的示例。
在本教程中,我们将为一个简单的博客设置 ACL。如果您尚未在 Nettuts+ 上查看 CakePHP 入门(和第 2 部分),请查看后返回,因为我们将理所当然地认为框架基础知识。
通过这个层次结构,我们可以为每个角色分配多个权限:
每个权限都会授予组,而不是用户;因此,如果用户 #6 晋升为管理员,系统将检查他的组权限——而不是他的权限。这些角色和子节点(用户)称为访问请求对象,或 ARO。
现在,在另一边,我们有了访问控制对象(ACO)。这些都是要控制的对象。上面我提到了帖子和用户。通常,这些对象与模型直接链接,因此如果我们有一个 Post 模型,我们将需要该模型的 ACO。
每个ACO都有四种基本权限:创建、读取、更新和删除。您可以使用关键字 CRUD 来记住它们。还有第五个权限,即星号,它是完全访问权限的快捷方式。
在本教程中,我们将仅使用两个 ACO:Post 和 User,但您可以根据需要创建任意多个 ACO。
让我们继续创建数据库表。您可以在应用程序的 config/sql
目录中的 db_acl.sql
中找到此代码。
CREATE TABLE acos ( id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, parent_id INTEGER(10) DEFAULT NULL, model VARCHAR(255) DEFAULT '', foreign_key INTEGER(10) UNSIGNED DEFAULT NULL, alias VARCHAR(255) DEFAULT '', lft INTEGER(10) DEFAULT NULL, rght INTEGER(10) DEFAULT NULL, PRIMARY KEY (id) ); CREATE TABLE aros_acos ( id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, aro_id INTEGER(10) UNSIGNED NOT NULL, aco_id INTEGER(10) UNSIGNED NOT NULL, _create CHAR(2) NOT NULL DEFAULT 0, _read CHAR(2) NOT NULL DEFAULT 0, _update CHAR(2) NOT NULL DEFAULT 0, _delete CHAR(2) NOT NULL DEFAULT 0, PRIMARY KEY(id) ); CREATE TABLE aros ( id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, parent_id INTEGER(10) DEFAULT NULL, model VARCHAR(255) DEFAULT '', foreign_key INTEGER(10) UNSIGNED DEFAULT NULL, alias VARCHAR(255) DEFAULT '', lft INTEGER(10) DEFAULT NULL, rght INTEGER(10) DEFAULT NULL, PRIMARY KEY (id) );
我们现在可以开始创建 ARO 和 ACO 节点,但是嘿,我们没有用户!我们必须创建一个基本的身份验证系统。
由于本教程面向具有基本到中等框架知识的 CakePHP 开发人员,因此我将提供代码和简要说明。但是,身份验证系统不是本教程的目标。
MySQL 表:
CREATE TABLE users ( id INTEGER(10) UNSIGNED AUTO_INCREMENT KEY, username TEXT, password TEXT );
用户模型 (models/user.php
)
<?php class User extends AppModel { var $name = 'User'; } ?>
用户控制器 (controllers/users_controller.php
)
<?php class UsersController extends AppController { var $name = 'Users'; var $components = array('Auth'); function beforeFilter(){ $this->Auth->userModel = 'User'; $this->Auth->allow('*'); } function register(){ if(!empty($this->data)){ // Here you should validate the username (min length, max length, to not include special chars, not existing already, etc) // As well as the password if($this->User->validates()){ $this->User->save($this->data); // Let's read the data we just inserted $data = $this->User->read(); // Use it to authenticate the user $this->Auth->login($data); // Then redirect $this->redirect('/'); } } } function login(){ if(!empty($this->data)){ // If the username/password match if($this->Auth->login($this->data)){ $this->redirect('/'); } else { $this->User->invalidate('username', 'Username and password combination is incorrect!'); } } } function logout(){ $this->Auth->logout(); $this->redirect('/'); } } ?>
既然我们有了新元素,让我们回顾一下它们。首先,我们设置一个 $components
变量。该变量包括数组中的所有组件。我们将需要 Auth 组件,它是一个核心组件,就像 HTML 和表单助手一样,但由于 Cake 默认情况下不包含它,所以我们必须手动包含它。
Auth 组件处理一些基本的身份验证机制:它帮助我们登录用户并为我们处理经过身份验证的用户会话,以及处理访客的注销和基本授权。此外,它还会自动对密码进行哈希处理。我将在下面的段落中解释如何调用每个函数。
接下来,我们创建一个名为 beforeFilter
的函数。这是一个回调函数,它允许我们在处理所有控制器逻辑之前设置一些操作。 Auth 组件要求我们指定要使用的模型,在本例中为 User。然后,默认情况下,它将拒绝所有未登录用户的访问。我们必须使用 allow()
覆盖此行为,该行为需要一个参数。该参数可以是星号,指定未经身份验证的用户可以访问所述控制器内的所有方法。或者,可以向它传递一个数组,其中包含未经身份验证的用户可以访问的函数。在本例中,由于我们只有三个函数,因此以下几行是相同的内容。
$this->Auth->allow('*'); $this->Auth->allow(array('register', 'login', 'logout'));
对于 login()
函数,Auth 组件将为我们处理所有登录机制。我们只需为该函数提供一个包含两个键的数组:用户名和密码。这些键可以更改,但默认情况下, username
和 password
字段都将与数据库进行匹配,如果用户已通过身份验证,则返回 true。
最后,控制器的登录功能将尝试将用户名/密码组合与数据库进行匹配。
请注意,此代码非常基础。您需要验证用户名字符、用户名是否存在、密码的最小长度等等。
注册视图 (views/users/register.ctp
)
<h2>Register your account</h2> <form method="POST" action="<?=$this->here; ?>"> <p> Username <?=$form->text('User.username'); ?> </p> <p> Password <?=$form->password('User.password'); ?> </p> <?=$form->submit('Register'); ?> </form>
登录视图 (views/users/login.ctp
)
<h2>Log in to your account</h2> <form method="POST" action="<?=$this->here; ?>"> <?=$form->error('User.username'); ?> <p> Username <?=$form->text('User.username'); ?> </p> <p> Password <?=$form->password('User.password'); ?> </p> <?=$form->submit('Log in'); ?> </form>
在网络浏览器中打开 /users/register
并注册一个新帐户。我建议 admin
作为用户名,123
作为密码,如果您的会话过期,只需转到 /users/login
并输入您刚刚创建的正确用户名/密码组合。
我们甚至没有使用 ACL,但我们已经可以拒绝发布、编辑和删除帖子。打开您的 Posts 控制器并添加 Auth 组件。
var $components = array('Auth');
现在在您的网络浏览器中访问 /posts
。如果您已登录,您应该会看到这些帖子,但如果您没有登录,您将被重定向到 /users/login
。通过简单地包含身份验证组件,默认情况下,所有操作都会拒绝来宾。我们需要拒绝未经授权的用户的三种操作:创建、编辑和删除。换句话说,我们必须允许索引和视图。
function beforeFilter(){ $this->Auth->userModel = 'User'; $this->Auth->allow(array('index', 'view')); }
去编辑或创建帖子;如果您尚未登录,您应该被重定向到 /users/login
。一切似乎都进展顺利,但是视图呢?编辑和删除链接正在向所有人显示。我们应该提出一个条件。
但在讨论之前,让我们看看 Auth 的 user() 函数是如何工作的。将这些行复制并粘贴到索引函数中。
$user = $this->Auth->user(); pr($user);
在浏览器中打开 /posts
,如果登录,则 pr()
将抛出类似这样的内容。
Array ( [User] => Array ( [id] => 1 [username] => admin ) )
user()
函数返回一个数组,就像模型一样。如果我们有超过三个字段(不包括密码),它们将显示在数组中。如果您没有登录,该数组将为空,因此如果 Auth 的 user()
数组不为空,您就可以知道用户已登录。
现在,Auth 是一个组件,旨在在控制器中使用。我们需要从视图中了解用户是否已登录,最好是通过帮助程序。我们如何在助手中使用组件? CakePHP 是如此出色和灵活,以至于这是可能的。
<? class AccessHelper extends Helper{ var $helpers = array("Session"); function isLoggedin(){ App::import('Component', 'Auth'); $auth = new AuthComponent(); $auth->Session = $this->Session; $user = $auth->user(); return !empty($user); } ?>
将此代码段保存在 views/helpers
中,命名为 access.php
。现在让我们逐行查看代码。首先,我们设置一个 $helpers
变量。帮助程序可以包含其他帮助程序,就像 $components
一样。 Auth 组件需要 Session 组件,但我们无法在助手中访问该组件。幸运的是,我们有一个会话助手,它将帮助我们。
接下来,我们创建一个函数并使用 App::import
,这将让我们导入一个通常我们无法访问的元素。下一行在 $auth
变量中创建 Auth 组件,现在是一个有点肮脏的黑客;由于 Auth 组件读取会话来了解我们是否已登录,因此它需要 Session 组件,但当我们从不应该属于它的位置导入它时,我们必须给它一个新的 Session 对象。最后,我们使用 user()
并将其设置为 $user
,如果变量不为空则返回 true,否则返回 false。
让我们回到帖子控制器并继续添加助手。
var $helpers = array('Access');
现在可以从视图访问访问助手。在 views/posts
中打开 index.ctp
并替换此行。
<small><a href="/posts/edit/<? echo $post['Post']['id'] ?>">edit</a> | <? echo $html->link('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Are you sure?'); ?></small>
有了这个。
<? if($access->isLoggedin()): ?><small><a href="/posts/edit/<? echo $post['Post']['id'] ?>">edit</a> | <? echo $html->link('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Are you sure?'); ?></small><? endif; ?>
返回网络浏览器,重新加载索引页,如果您已登录,您将看到每个帖子的编辑和删除链接。否则,您将看不到任何内容。
虽然如果您的应用程序只有一两个用户,这就足够了,但如果您开放注册,这还不够。
打开用户控制器并添加 ACL 组件。
var $components = array('Auth', 'Acl');
接下来,让我们创建一个函数来安装 ACO 和 ARO 节点。
function install(){ if($this->Acl->Aro->findByAlias("Admin")){ $this->redirect('/'); } $aro = new aro(); $aro->create(); $aro->save(array( 'model' => 'User', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Super' )); $aro->create(); $aro->save(array( 'model' => 'User', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Admin' )); $aro->create(); $aro->save(array( 'model' => 'User', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'User' )); $aro->create(); $aro->save(array( 'model' => 'User', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Suspended' )); $aco = new Aco(); $aco->create(); $aco->save(array( 'model' => 'User', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'User' )); $aco->create(); $aco->save(array( 'model' => 'Post', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Post' )); $this->Acl->allow('Super', 'Post', '*'); $this->Acl->allow('Super', 'User', '*'); $this->Acl->allow('Admin', 'Post', '*'); $this->Acl->allow('User', 'Post', array('create')); }
通过导入ACL组件,我们可以访问ACO和ARO模型。我们首先创建一个 ARO,它是对象的请求者;换句话说,谁将访问这些对象。这就是用户(因此是模型中的用户字符串)。我们正在创造不同的角色;超级、管理员、用户和暂停。
接下来,我们对 ACO 执行相同的操作,因为我们只有两个对象要管理(帖子和用户),所以我们将创建两个对象,每个对象一个。
现在,快速说明一下。我们为 ACO 和 ARO 保存的数组有四个字段:模型名称、外键(如果您想授予对一个 ARO 的访问权限,例如他创建的帖子,则该字段很有用)、父 ID(稍后将使用)和 是基本的父子节点关系;我们将在这些角色下创建用户)和别名(这是查找对象的快速形式)。
最后,我们使用 ACL 的 allow()
函数。第一个参数是ACO别名;第二,ARO 别名,第三,授予所述 ARO 的权限。超级用户可以完全访问帖子和用户模型,管理员可以完全访问帖子模型,用户只能创建帖子。
在函数的开头,我声明了一个条件来检查 ACO 中是否存在管理员角色。您不想多次安装相同的东西,对吗?这会严重扰乱数据库。
在 Web 浏览器中打开 /users/install
,由于我们没有视图,CakePHP 会抛出错误,但只需检查 MySQL 转储即可。所有关系都已成功创建,是时候处理子节点了。
让我们清理 users
表。打开 phpMyAdmin,选择您的数据库,users
表,然后单击清空。我们将回到用户控制器上的 register()
函数。就在这一行下方:
$this->Auth->login($data);
粘贴此代码:
// Set the user roles $aro = new Aro(); $parent = $aro->findByAlias($this->User->find('count') > 1 ? 'User' : 'Super'); $aro->create(); $aro->save(array( 'model' => 'User', 'foreign_key' => $this->User->id, 'parent_id' => $parent['Aro']['id'], 'alias' => 'User::'.$this->User->id ));
在第一行中,我们创建一个新的 ARO 对象。然后我们将获取将在其中创建用户的父节点。如果数据库中有记录,我们将其设置为用户 ARO,否则,第一个用户应该是 Super。
然后我们保存一个新的ARO;该模型是我们当前正在开发的模型,User
,foreign_key
是我们刚刚创建的最后一条记录的 id。 parent_id
是我们开始的节点;我们只传递 id 和最后的别名。最好将其命名为 Model_name
,然后是分隔符 ::
,然后是标识符。找到它会容易得多。
现在我们完成了。创建四个用户:超级用户、管理员用户、普通用户和暂停用户。我建议使用相同的用户名和密码进行测试。不要忘记,到目前为止,只有超级用户具有 Super 角色;剩下的都是用户!
由于 ACL 是一个组件,因此只能在控制器内访问它。稍后,它也会出现在视野中;但首先要紧的事情。将 ACL 组件包含在 Posts 控制器中,就像我们在 Users 控制器中所做的那样。现在您可以开始检查权限。让我们进入编辑功能并进行快速测试。在该方法的第一行添加此内容。
$user = $this->Auth->user(); if(!$this->Acl->check('User::'.$user['User']['id'], 'Post', 'update')) die('you are not authorized');
ACL 的 check()
函数需要三个参数:ARO、ACO 和操作。去编辑帖子,如果您没有以超级用户身份登录,脚本就会死掉。转到 /users/login
并以超级用户身份访问并返回编辑。您应该能够编辑该帖子。检查下面的 MySQL 转储,看看它的神奇之处。四个数据库查询:这肯定是不可扩展的。
我们有两个问题。首先,两行用于权限检查。其次,ACL 没有被缓存。并且不要忘记所有登录用户都可以看到编辑链接,即使只有超级用户可以使用它。
让我们创建一个新组件。我们称之为 Access。
<?php class AccessComponent extends Object{ var $components = array('Acl', 'Auth'); var $user; function startup(){ $this->user = $this->Auth->user(); } } ?>
将其保存在controllers/components
中,命名为access.php
。该类的 $user
var 将在组件加载时启动,而 startup()
是一个回调函数,因此现在在该类中设置。现在让我们创建 check()
函数。
function check($aco, $action='*'){ if(!empty($this->user) && $this->Acl->check('User::'.$this->user['User']['id'], $aco, $action)){ return true; } else { return false; } }
我们的 check()
方法只需要两个参数:ACO 和操作(可选)。 ARO 将是每个会话的当前用户。操作参数默认为 *
,这是 ARO 的完全访问权限。该函数首先检查 $this->user
是否不为空(这实际上告诉我们用户是否已登录),然后转到 ACL。我们已经介绍过这一点。
我们现在可以将 Access 组件包含在我们的 Posts 控制器中,并只需一行即可检查权限。
if(!$this->Access->check('Post', 'update')) die('you are not authorized');
用更少的代码可以达到相同的结果,但错误消息很难看。您最好将 die()
替换为 CakePHP 错误处理程序:
$this->cakeError('error404');
这可能很难看,但它确实有效。我们必须创建一个帮助程序,使用自定义方法加载组件以供在帮助程序中使用。
在Access组件(controllers/components/access.php
)中添加此函数。
function checkHelper($aro, $aco, $action = "*"){ App::import('Component', 'Acl'); $acl = new AclComponent(); return $acl->check($aro, $aco, $action); }
现在,让我们重写访问助手(views/helpers/access.php
)。
<?php class AccessHelper extends Helper{ var $helpers = array("Session"); var $Access; var $Auth; var $user; function beforeRender(){ App::import('Component', 'Access'); $this->Access = new AccessComponent(); App::import('Component', 'Auth'); $this->Auth = new AuthComponent(); $this->Auth->Session = $this->Session; $this->user = $this->Auth->user(); } function check($aco, $action='*'){ if(empty($this->user)) return false; return $this->Access->checkHelper('User::'.$this->user['User']['id'], $aco, $action); } function isLoggedin(){ return !empty($this->user); } } ?>
beforeRender()
方法是一个回调,类似于组件的startup()
。我们正在加载两个组件,由于它们将在大多数函数中使用,因此最好一次启动所有组件,而不是每次调用方法时手动启动它们。
现在,在 views/posts
中的 index.ctp
视图中,您可以替换此行。
<small><a href="/posts/edit/<? echo $post['Post']['id'] ?>">edit</a> | <? echo $html->link('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Are you sure?'); ?></small>
有了这个。
<? if($access->check('Post')): ?><small><a href="/posts/edit/<? echo $post['Post']['id'] ?>">edit</a> | <? echo $html->link('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Are you sure?'); ?></small><? endif; ?>
只是不要忘记检查视图和控制器中的权限!
您可以使用 Auth 组件中的 user()
方法访问已登录用户的用户数据。然后您可以访问该数组并获取您想要的信息。但一定有更好的方法。让我们在 Access 组件中添加以下函数。
function getmy($what){ return !empty($this->user) && isset($this->user['User'][$what]) ? $this->user['User'][$what] : false; }
当您需要保存具有 user_id
关系的帖子时,这非常有用。
$this->data['Post']['user_id'] = $this->Access->getmy('id');
在视图中,我们可以使用助手做类似的事情。
function getmy($what){ return !empty($this->user) && isset($this->user['User'][$what]) ? $this->user['User'][$what] : false; }
在模板文件中,您可以执行如下操作,通过用户名向用户打招呼。
Welcome <?=$access->isLoggedIn() ? $access->getmy('username') : 'Guest'; ?>
假设我们需要为用户 #4 切换角色:他需要成为超级用户。因此,User 的 id 是 4,Aro 的 id 是 1。
$user_id = 4; $user_new_group = 1;
现在我们需要找到用户的 Aro 以便修改其父 Aro。
$aro_user = $this->Acl->Aro->find('first', array( 'conditions' => array( 'Aro.parent_id !=' => NULL, 'Aro.model' => 'User', 'Aro.foreign_key' => $user_id ) ) );
最后,如果 $aro_user
变量不为空,让我们更新 Aro.parent_id
字段。
if(!empty($aro_user)){ $data['id'] = $aro_user['Aro']['id']; $data['parent_id'] = $user_new_group; $this->Acl->Aro->save($data); }
请注意,您必须验证用户的 ID 和新 aro 的 ID。如果其中之一不存在,则会在您的 ACL 表中造成混乱。
虽然我们刚刚创建的组件既简单又有用,但每次检查都会查询数据库。目前尚未准备好投入生产。首先,应尽可能少地查询数据库。为了优化这一点,我们应该利用 CakePHP 的缓存。
使用缓存会大大减少负载,但如果我们有十个帖子出现在索引中,并且对于每个帖子,我们都会检查用户是否具有帖子 Aco 的更新权限,框架将读取并解析一个文件返回相同的结果...每个页面加载十次。
这是第二点:类内的变量和一些条件将使工作更轻松,以便重复的请求将仅检查 Cache 一次。
这两个更改都反映在 access_cache.php
中,位于 controllers/components
目录中。因此,请确保下载源代码!
访问控制列表是大多数应用程序需要的基本功能。 CakePHP 有一个很好的实现,但缺乏良好的文档和示例。我希望通过本教程能够解决这两个问题。感谢您的阅读!
以上是CakePHP 访问控制列表:使用指南的详细内容。更多信息请关注PHP中文网其他相关文章!