CakePHP 访问控制列表:使用指南

WBOY
发布: 2023-08-27 12:34:01
原创
567 人浏览过

CakePHP 访问控制列表:使用指南

如果您正在构建 CMS,您可能需要具有不同权限级别的不同用户角色(超级用户、管理员、用户)。代码太复杂?输入 CakePHP 的 ACL(访问控制列表)。通过正确的设置,您只需一行即可检查用户权限。

简介:什么是访问控制列表?

ACL 允许您创建具有各自角色的用户层次结构。这是一个简单的示例。

  • 超级用户
    • 用户 #1
  • 管理员
    • 用户#2
    • 用户#3
  • 用户
    • 用户#4
    • 用户#5
    • 用户#6
    • ...

在本教程中,我们将为一个简单的博客设置 ACL。如果您尚未在 Nettuts+ 上查看 CakePHP 入门(和第 2 部分),请查看后返回,因为我们将理所当然地认为框架基础知识。

通过这个层次结构,我们可以为每个角色分配多个权限:

  • 超级用户可以创建、阅读、更新和删除帖子和用户。
  • 管理员可以创建、阅读、更新和删除帖子。
  • 用户可以创建和阅读帖子。
  • 其他人都可以阅读帖子。

每个权限都会授予组,而不是用户;因此,如果用户 #6 晋升为管理员,系统将检查他的组权限——而不是他的权限。这些角色和子节点(用户)称为访问请求对象,或 ARO。

现在,在另一边,我们有了访问控制对象(ACO)。这些都是要控制的对象。上面我提到了帖子和用户。通常,这些对象与模型直接链接,因此如果我们有一个 Post 模型,我们将需要该模型的 ACO。

每个ACO都有四种基本权限:创建、读取、更新和删除。您可以使用关键字CRUD来记住它们。还有第五个权限,即星号,它是完全访问权限的快捷方式。

在本教程中,我们将仅使用两个 ACO:Post 和 User,但您可以根据需要创建任意多个 ACO。

ACL 表

让我们继续创建数据库表。您可以在应用程序的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 节点,但是嘿,我们没有用户!我们必须创建一个基本的身份验证系统。


第 1 步:基本身份验证系统

由于本教程面向具有基本到中等框架知识的 CakePHP 开发人员,因此我将提供代码和简要说明。但是,身份验证系统不是本教程的目标。

MySQL 表:

CREATE TABLE users ( id INTEGER(10) UNSIGNED AUTO_INCREMENT KEY, username TEXT, password TEXT );
登录后复制

用户模型(models/user.php)

登录后复制

用户控制器(controllers/users_controller.php)

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 组件将为我们处理所有登录机制。我们只需为该函数提供一个包含两个键的数组:用户名和密码。这些键可以更改,但默认情况下,usernamepassword字段都将与数据库进行匹配,如果用户已通过身份验证,则返回 true。

最后,控制器的登录功能将尝试将用户名/密码组合与数据库进行匹配。

请注意,此代码非常基础。您需要验证用户名字符、用户名是否存在、密码的最小长度等等。

注册视图(views/users/register.ctp)

Register your account

Username text('User.username'); ?>

Password password('User.password'); ?>

submit('Register'); ?>
登录后复制

登录视图(views/users/login.ctp)

Log in to your account

error('User.username'); ?>

Username text('User.username'); ?>

Password password('User.password'); ?>

submit('Log in'); ?>
登录后复制

在网络浏览器中打开/users/register并注册一个新帐户。我建议admin作为用户名,123作为密码,如果您的会话过期,只需转到/users/login并输入您刚刚创建的正确用户名/密码组合。


第 2 步:拒绝未经身份验证的用户访问

我们甚至没有使用 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 是如此出色和灵活,以至于这是可能的。

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并替换此行。

edit | link('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Are you sure?'); ?>
登录后复制
登录后复制

有了这个。

isLoggedin()): ?>edit | link('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Are you sure?'); ?>
登录后复制

返回网络浏览器,重新加载索引页,如果您已登录,您将看到每个帖子的编辑和删除链接。否则,您将看不到任何内容。

虽然如果您的应用程序只有一两个用户,这就足够了,但如果您开放注册,这还不够。


第 3 步:安装 ACL

打开用户控制器并添加 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;该模型是我们当前正在开发的模型,Userforeign_key是我们刚刚创建的最后一条记录的 id。parent_id是我们开始的节点;我们只传递 id 和最后的别名。最好将其命名为Model_name,然后是分隔符::,然后是标识符。找到它会容易得多。

现在我们完成了。创建四个用户:超级用户、管理员用户、普通用户和暂停用户。我建议使用相同的用户名和密码进行测试。不要忘记,到目前为止,只有超级用户具有 Super 角色;剩下的都是用户!


第4步:阅读权限

由于 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。

user = $this->Auth->user(); } } ?>
登录后复制

将其保存在controllers/components中,命名为access.php。该类的$uservar 将在组件加载时启动,而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)。

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视图中,您可以替换此行。

edit | link('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Are you sure?'); ?>
登录后复制
登录后复制

有了这个。

check('Post')): ?>edit | link('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Are you sure?'); ?>
登录后复制

只是不要忘记检查视图和控制器中的权限!

用户数据

您可以使用 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 isLoggedIn() ? $access->getmy('username') : 'Guest'; ?>
登录后复制

第5步:修改权限

假设我们需要为用户 #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 表中造成混乱。


第 6 步:优化

虽然我们刚刚创建的组件既简单又有用,但每次检查都会查询数据库。目前尚未准备好投入生产。首先,应尽可能少地查询数据库。为了优化这一点,我们应该利用 CakePHP 的缓存。

使用缓存会大大减少负载,但如果我们有十个帖子出现在索引中,并且对于每个帖子,我们都会检查用户是否具有帖子 Aco 的更新权限,框架将读取并解析一个文件返回相同的结果...每个页面加载十次。

这是第二点:类内的变量和一些条件将使工作更轻松,以便重复的请求将仅检查 Cache 一次。

这两个更改都反映在access_cache.php中,位于controllers/components目录中。因此,请确保下载源代码!


结论

访问控制列表是大多数应用程序需要的基本功能。 CakePHP 有一个很好的实现,但缺乏良好的文档和示例。我希望通过本教程能够解决这两个问题。感谢您的阅读!

以上是CakePHP 访问控制列表:使用指南的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:php.cn
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!