Documentation

访问控制列表 ACL(Access Control Lists ACL)

Phalcon在权限方面通过 Phalcon\Acl 提供了一个轻量级的 ACL(访问控制列表)。Access Control Lists (ACL) 允许系统对用户的访问权限进行控制,比如允许访问某些资源而不允许访问其它资源等。这里我们建议开发者了解一些关于ACL的技术。

ACL有两部分组成即角色和资源。资源即是ACL定义的权限所依附的对象。角色即是ACL所字义的请求者的身份,ACL决定了角色对资源的访问权限,允许访问或拒绝访问。

创建 ACL(Creating An ACL)

这个组件起先是设计工作在内存中的, 这样做提供了更高的访问速度。 Phalcon\Acl 构造器的第一个参数用于设置取得ACL的方式。 下面是使用内存适配器的例子:

<?php

use Phalcon\Acl\Adapter\Memory as AclList;

$acl = new AclList();

默认情况下 Phalcon\Acl 允许我们访问未定义的资源中的action,为了提高安全性, 我们设置默认访问级别为‘拒绝’。

<?php

// 设置默认访问级别为拒绝
$acl->setDefaultAction(Phalcon\Acl::DENY);

添加角色(Adding Roles to the ACL)

角色即是权限的集合体,其中定义了我们对资源的访问权限。 例如, 我们会把一个组织内的不同的人定义为不同的角色。 The Phalcon\Acl\Role 类使用一种更有组织的方式来定义角色。 这里我们创建一些角色:

<?php

use Phalcon\Acl\Role;

// 创建角色
// The first parameter is the name, the second parameter is an optional description.
$roleAdmins = new Role("Administrators", "Super-User role");
$roleGuests = new Role("Guests");

// 添加 "Guests" 角色到ACL
$acl->addRole($roleGuests);

// 添加"Designers"到ACL, 仅使用此字符串。
$acl->addRole("Designers");

上面我们看到,我们可以直接使用字符串来定义角色。

添加资源(Adding Resources)

资源即是访问控制要控制的对象之一。 正常情况下在mvc中资源一般是控制器。 Phalcon中我们使用 Phalcon\Acl\Resource 来定义资源。 非常重要的一点即是我们把相关的action或操作添加到资源中这样ACL才知道控制什么资源。

<?php

use Phalcon\Acl\Resource;

// 定义 "Customers" 资源
$customersResource = new Resource("Customers");

// 为 "customers"资源添加一组操作
$acl->addResource($customersResource, "search");
$acl->addResource($customersResource, array("create", "update"));

定义访问控制(Defining Access Controls)

至此我们定义了角色及资源, 现在是定义ACL的时候了,即是定义角色对资源的访问。 这个部分是极其重要的,特别是在我们设定了默认的访问级别后。

<?php

// 设置角色对资源的访问级别
$acl->allow("Guests", "Customers", "search");
$acl->allow("Guests", "Customers", "create");
$acl->deny("Guests", "Customers", "update");

allow()方法指定了允许角色对资源的访问, deny()方法则反之。

我们还可以指定第四个参数,设置回调函数,来处理来自 isAllowed() 方法的数据:

<?php

// 设置角色对资源的访问级别
$acl->allow("Guests", "Customers", "search", function ($role, $resource, $access, $data, $allow) {
    return $data % 2 === 0;
});

// Returns true
$acl->isAllowed("Guests", "Customers", "search", 4);

查询 ACL(Querying an ACL)

一旦访问控制表定义之后, 我们就可以通过它来检查角色是否有访问权限了。

<?php

// 查询角色是否有访问权限
$acl->isAllowed("Guests", "Customers", "edit");   // Returns 0
$acl->isAllowed("Guests", "Customers", "search"); // Returns 1
$acl->isAllowed("Guests", "Customers", "create"); // Returns 1

使用角色感知对象与资源感知对象(Objects as role name and resource name)

角色感知类必须实现 Phalcon\Acl\RoleAware 接口,资源感知类必须实现 Phalcon\Acl\ResourceAware 接口。

角色感知类 UserRoles

<?php

class UserRoles extends Phalcon\Mvc\Model implements Phalcon\Acl\RoleAware
{
    public function getId()
    {
        return $this->id;
    }

    // Implemented function from RoleAware Interface
    public function getRoleName()
    {
        return $this->rolename;
    }
}

资源感知类 AclResources

<?php

class AclResources extends Phalcon\Mvc\Model implements Phalcon\Acl\ResourceAware
{
    public function getId()
    {
        return $this->id;
    }

    public function getUserId()
    {
        return $this->user_id;
    }

    // Implemented function from ResourceAware Interface
    public function getResourceName()
    {
        return $this->resourcename;
    }
}

我们可以在 isAllowed() 方法中使用它们:

<?php

$acl = new Phalcon\Acl\Adapter\Memory;
$acl->allow("Guests", "Customers", "search");
$acl->allow("Guests", "Customers", "create");
$acl->deny("Guests", "Customers", "update");

// resourcename is "Customers", user_id is 2
$customer = AclResources::findFirst(1);

// rolename is "Designers"
$designer = UserRoles::findFirst(1);
// rolename is "Guests"
$guest = UserRoles::findFirst(2);
// rolename is "Guests"
$anotherGuest = UserRoles::findFirst(3);

// Returns false
$acl->isAllowed($designer, $customer, "search");

// Returns true
$acl->isAllowed($guest, $customer, "search");

// Returns true
$acl->isAllowed($anotherGuest, $customer, "search");

我们还可以在 allow()deny() 方法自定义回调函数,来处理角色和资源:

<?php

$acl = new Phalcon\Acl\Adapter\Memory;
$acl->allow("Guests", "Customers", "search", function (UserRoles $user, AclResources $resource) {
    return $user->getId() == $resource->getUserId();
});

// Returns false
$acl->isAllowed($designer, $customer, "search");

角色继承(Roles Inheritance)

我们可以使用 Phalcon\Acl\Role 提供的继承机制来构造更复杂的角色。 Phalcon中的角色可以继承来自其它角色的 权限, 这样就可以实现更巧妙的资源访问控制。 如果要继承权限用户, 我们需要在添加角色函数的第二个参数中写上要继承的那个角色实例。

<?php

use Phalcon\Acl\Role;

// ...

// 创建角色
$roleAdmins = new Role("Administrators", "Super-User role");
$roleGuests = new Role("Guests");

// 添加 "Guests" 到 ACL
$acl->addRole($roleGuests);

// 使Administrators继承Guests的访问权限
$acl->addRole($roleAdmins, $roleGuests);

序列化 ACL 列表(Serializing ACL lists)

为了提高性能, Phalcon\Acl 的实例可以被实例化到APC, session, 文本或数据库中, 这样开发者就不需要重复的 定义acl了。 下面展示了如何去做:

<?php

use Phalcon\Acl\Adapter\Memory as AclList;

// ...

// 检查ACL数据是否存在
if (!is_file("app/security/acl.data")) {

    $acl = new AclList();

    // ... Define roles, resources, access, etc

    // 保存实例化的数据到文本文件中
    file_put_contents("app/security/acl.data", serialize($acl));
} else {

     // 返序列化
     $acl = unserialize(file_get_contents("app/security/acl.data"));
}

// 使用ACL
if ($acl->isAllowed("Guests", "Customers", "edit")) {
    echo "Access granted!";
} else {
    echo "Access denied :(";
}

It’s recommended to use the Memory adapter during development and use one of the other adapters in production.

ACL 事件(ACL Events)

如果需要的话 Phalcon\Acl 可以发送事件到 EventsManager 。 这里我们为acl绑定事件。 其中一些事件的处理结果如果返回了false则表示正在处理的操作会被中止。 支持如下的事件:

事件名 触发条件 能否中止操作
beforeCheckAccess 在权限检查之前触发 Yes
afterCheckAccess 在权限检查之后触发 No

下面的例子中展示了如何绑定事件到此组件:

<?php

use Phalcon\Acl\Adapter\Memory as AclList;
use Phalcon\Events\Manager as EventsManager;

// ...

// 创建事件管理器
$eventsManager = new EventsManager();

// 绑定事件类型为acl
$eventsManager->attach("acl", function ($event, $acl) {
    if ($event->getType() == "beforeCheckAccess") {
         echo   $acl->getActiveRole(),
                $acl->getActiveResource(),
                $acl->getActiveAccess();
    }
});

$acl = new AclList();

// Setup the $acl
// ...

// 绑定eventsManager到ACL组件
$acl->setEventsManager($eventManagers);

自定义适配器(Implementing your own adapters)

开发者要创建自己的扩展或已存在适配器则需要实现此 Phalcon\Acl\AdapterInterface 接口。