简单的 Acl 控制的应用

    这不是初学者的教程。如果你刚刚开始学习 CakePHP,我们建议你还是先对整个框架的特点全面了解之后再开始本教程。

    在这个教程中,你将会使用 Authentication 和 创建一个简单的应用。这个教程假设你已经阅读过 博客教程,并且熟悉。你应该对 CakePHP 有了一些经验, 并且熟悉 MVC 概念。这个教程是对 和 的一个简单介绍。

    你所需要的:

    • 一个可以运行的 web 服务器。我们将假定你使用的是 Apache,不过使用其他服务器的步骤也差不多。我们也许要稍微调整服务器的配置,但大多数人完全不需要任何配置就可以让 CakePHP 运行起来。
    • 一个数据库服务器。在本教程中我们将使用 MySQL 数据库。你将会需要对 SQL 有足够的了解以便创建一个数据库:接下来就是 CakePHP 的事情了。
    • PHP 的基础知识。你使用面向对象编程越多越好,但如果你只熟悉面向过程编程也不要害怕。

    首先,让我们获取一份最新的 CakePHP 的代码。

    要获得最新的代码,请访问在 GitHub 上的 CakePHP 项目:https://github.com/cakephp/cakephp/tags,并下载稳定发行版。对于本教程,你需要最新的 2.0 发行版本。

    你也可以使用 检出最新的代码:

    一旦你获得了最新的 CakePHP 代码,改变分支到最新的 2.0 版本,设置配置文件database.php,修改 app/Config/core.php 文件中的 Security.salt 的值。然后我们会建立一个简单的数据库结构,作为应用程序的基础。在数据库中执行如下的 SQL 语句:

    1. CREATE TABLE users (
    2. id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    3. username VARCHAR(255) NOT NULL UNIQUE,
    4. password CHAR(40) NOT NULL,
    5. group_id INT(11) NOT NULL,
    6. created DATETIME,
    7. modified DATETIME
    8. );
    9.  
    10.  
    11. CREATE TABLE groups (
    12. id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    13. name VARCHAR(100) NOT NULL,
    14. created DATETIME,
    15. modified DATETIME
    16. );
    17.  
    18.  
    19. CREATE TABLE posts (
    20. id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    21. user_id INT(11) NOT NULL,
    22. title VARCHAR(255) NOT NULL,
    23. body TEXT,
    24. created DATETIME,
    25. modified DATETIME
    26. );
    27.  
    28. CREATE TABLE widgets (
    29. id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    30. name VARCHAR(100) NOT NULL,
    31. part_no VARCHAR(12),
    32. quantity INT(11)
    33. );

    这些是我们构建应用程序的其余部分要用到的表。一旦数据库中有了表结构,我们就可以开始了。使用 Code Generation with Bake 快速创建你的模型、控制器和视图。

    要使用 cake bake,调用 cake bake all,这会列出你插入到 MySQL 中的4个表,选择"1. Group" 并按照提示操作。对其他 3 个表也进行同样的操作,这会为你生成 4 个控制器、模型和相应的视图。

    在这里要避免使用脚手架(Scaffold)。如果生成带有脚手架功能的控制器,将会严重影响ACO(Access Control Object)的生成。

    当自动生成模型代码时,cake 会自动探测出模型之间的关联(即表之间的关系)。让 cake提供正确的 hasMany 和 belongsTo 关系。如果提示你选择 hasOne 或者 hasMany 关系,在本教程中通常(只)需要 hasMany 关系。

    现在先不管 admin 路由,没有它们这个话题已经够复杂的了。另外,在用 bake 生成控制器时,一定 不要 添加 Acl 或者 Auth 组件到任何控制器中。我们很快就会着手于此。你现在应该已经有了 users、groups、posts 和 widgets 的模型、控制器以及生成的视图。

    1. public function login() {
    2. if ($this->request->is('post')) {
    3. return $this->redirect($this->Auth->redirectUrl());
    4. }
    5. $this->Session->setFlash(__('Your username or password was incorrect.'));
    6. }
    7. }
    8.  
    9. public function logout() {
    10. //现在先空着。
    11. }

    然后,为 login 动作创建如下所示的视图文件 app/View/Users/login.ctp:

    1. <?php
    2. echo $this->Form->create('User', array('action' => 'login'));
    3. echo $this->Form->inputs(array(
    4. 'legend' => __('Login'),
    5. 'username',
    6. 'password'
    7. ));
    8. echo $this->Form->end('Login');
    9. ?>

    接下来,我们需要更新我们的 User 模型,在保存到数据库之前先将密码散列化。存储普通文本格式的密码是极其危险的,并且 AuthComponent 组件会期望你的密码是经过散列化过的。在 app/Model/User.php 文件中添加如下代码:

    1. App::uses('AuthComponent', 'Controller/Component');
    2. class User extends AppModel {
    3. // 其它代码。
    4.  
    5. public function beforeSave($options = array()) {
    6. $this->data['User']['password'] = AuthComponent::password(
    7. $this->data['User']['password']
    8. );
    9. return true;
    10. }
    11. }

    接下来要改动一下 AppController。如果还没有/app/Controller/AppController.php,就创建该文件。因为我们要使用 Auth 和 Acl组件控制整个网站,所以我们会在 AppController 中把它们设置好:

    在设置 ACL 组件之前,需要添加一些用户和组。因为启用了 组件,我们无法访问任何动作,因为还没有登录。现在我们添加一些特例,这样AuthComponent 组件就会允许我们创建一些组和用户。在GroupsController 控制器和 UsersController 控制器中 添加:

    1. public function beforeFilter() {
    2. parent::beforeFilter();
    3.  
    4. // 对 CakePHP 2.0
    5. $this->Auth->allow('*');
    6.  
    7. // 对 CakePHP 2.1 及以上版本
    8. $this->Auth->allow();
    9. }

    这些语句告诉 AuthComponent 组件,允许公开访问所有动作。这只是临时的,一旦我们在数据库中有了一些用户和组之后就会去掉。只是现在还不要添加任何用户或组。

    在我们创建任何用户或者组之前,我们要把它们连接到 Acl 组件。不过,我们现在还没有任何 Acl 组件的表,如果你现在试图访问任何页面,你可能会得到表不存在的错误("Error: Database table acos for model Aco was not found.")。要消除这些错误,我们需要运行一个数据结构(schema)文件。在命令行执行下面的命令:

    1. ./Console/cake schema create DbAcl

    这个脚本会提示你删除并新建表。对删除和创建表的请求回答 yes。

    如果你没有访问外壳(shell)的权限,或者无法使用终端,你可以执行 sql 文件/path/to/app/Config/Schema/db_acl.sql。

    为数据输入设置了控制器,也初始化了 Acl 组件的表,这就行了吗?还不够,还需要在用户(user)和组(group)模型中稍做改动,也就是说,让他们自动地附加上 Acl 组件。

    为了让 Auth 组件和 Acl 组件正常工作,我们需要将用户(users)表和组(groups)表同Acl 组件的表中的记录进行关联。为此需要用到 AclBehavior 行为。允许将模型自动连接到 Acl 组件的表。使用它需要在模型中实现 parentNode() 方法。在 User 模型中添加如下代码:

    1. class User extends AppModel {
    2. public $belongsTo = array('Group');
    3. public $actsAs = array('Acl' => array('type' => 'requester'));
    4.  
    5. public function parentNode() {
    6. return null;
    7. }
    8. if (isset($this->data['User']['group_id'])) {
    9. $groupId = $this->data['User']['group_id'];
    10. } else {
    11. $groupId = $this->field('group_id');
    12. }
    13. if (!$groupId) {
    14. return null;
    15. }
    16. return array('Group' => array('id' => $groupId));
    17. }
    18. }

    然后在 Group 模型中添加如下代码:

    1. class Group extends AppModel {
    2. public $actsAs = array('Acl' => array('type' => 'requester'));
    3.  
    4. public function parentNode() {
    5. return null;
    6. }
    7. }

    我们所做的,就是将 GroupUser 模型与 Acl 组件联系起来,并告诉 CakePHP每次你创建一个用户(User)或组(Group)的同时也要在 aros 表中创建一条记录。这使得 Acl 的管理轻而易举,因为 ARO 透明地与 usersgroups 表绑定在一起了。所以,每次创建或者删除一个用户/组的同时,Aro 表也会更新。

    • administrators
    • managers
    • users
      我同时也在每个组中创建了一个用户,这样每个不同访问权限组都有一个用户,用于之后的测试。(把这些组和用户)全部记录下来,或者选用简单的密码,以免忘记。如果在 MySQL提示符后运行 SELECT * FROM aros;,应该可以看到象下面这样的记录:

    这告诉我们已经有了 3 个组和 3 个用户。用户嵌套在组中,这样我们就可以按组或者按用户设置权限。

    如果我们要简单一些,只按组设置的权限,需要在 User 模型中实现 bindNode()方法:

    1. public function bindNode($user) {
    2. return array('model' => 'Group', 'foreign_key' => $user['User']['group_id']);
    3. }

    然后修改 模型的 actsAs 变量,禁用 requester 指令:

    1. public $actsAs = array('Acl' => array('type' => 'requester', 'enabled' => false));

    这两处改动会告诉 ACL 忽略检查 User Aro,而只检查 Group Aro's。这样也避免了对 afterSave 回调的调用。

    注意:每个用户都需要设置 group_id 才行。

    现在 aros 表会是这样:

    1. +----+-----------+-------+-------------+-------+------+------+
    2. | id | parent_id | model | foreign_key | alias | lft | rght |
    3. +----+-----------+-------+-------------+-------+------+------+
    4. | 1 | NULL | Group | 1 | NULL | 1 | 2 |
    5. | 2 | NULL | Group | 2 | NULL | 3 | 4 |
    6. | 3 | NULL | Group | 3 | NULL | 5 | 6 |
    7. +----+-----------+-------+-------------+-------+------+------+
    8. 3 rows in set (0.00 sec)

    注意:如果你到这里一直跟随此教程,你需要删除你的表,包括 arosgroupsusers,然后从头重新创建组和用户,才能得到上面的 aros 表。

    现在我们已经有了用户和组(aro),我们可以开始把现有的控制器输入到 Acl 中,并对组和用户设置权限,并启用登录/登出。

    我们的 ARO 会在新建户和组的时候自动创建。有没有什么办法从控制器和动作来自动创建ACO?可惜 CakePHP 的核心没有这样的魔法。不过核心类提供了一些方法来手动创建 ACO。你可以通过 Acl 外壳程序或者 AclComponent 组件创建 ACO。从外壳程序创建 Aco:

    1. ./Console/cake acl create aco root controllers

    而使用 AclComponent 组件就是:

    上面两个例子都会创建 'root' 或者顶层 ACO,会叫做 'controllers' 。这个根(root)节点的目的,是为了在整个应用程序的范围内更容易地允许/拒绝访问,并且允许把 Acl组件用于和控制器/动作无关的目的,比如检查模型记录的访问权限。既然我们要使用全局的根(root) ACO,我们要略微修改 AuthComponent 组件的配置。AuthComponent组件需要知道这个根节点的存在,这样当进行 ACL 检查的时候,它可以在查找控制器/动作时使用正确的节点路径。在 AppController 中确保 数组中包含先前定义的 actionPath:

    1. class AppController extends Controller {
    2. public $components = array(
    3. 'Acl',
    4. 'Auth' => array(
    5. 'authorize' => array(
    6. 'Actions' => array('actionPath' => 'controllers')
    7. )
    8. ),
    9. );

    本教程在 中继续。