CMS 教程 - 授权

现在用户可以登录我们的 CMS,我们希望应用授权规则以确保每个用户只能编辑他们自己的帖子。我们将使用 授权插件 来实现这一点。

安装授权插件

使用 Composer 安装授权插件

composer require "cakephp/authorization:^3.0"

通过将以下语句添加到 src/Application.php 中的 bootstrap() 方法来加载插件

$this->addPlugin('Authorization');

启用授权插件

授权插件作为中间件层集成到您的应用程序中,并且可以选择作为组件以使检查授权更容易。首先,让我们应用中间件。在 src/Application.php 中,将以下内容添加到类导入中

use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Policy\OrmResolver;

AuthorizationServiceProviderInterface 添加到您应用程序上实现的接口

class Application extends BaseApplication
    implements AuthenticationServiceProviderInterface,
    AuthorizationServiceProviderInterface

然后将以下内容添加到您的 middleware() 方法中

// Add authorization **after** authentication
$middlewareQueue->add(new AuthorizationMiddleware($this));

AuthorizationMiddleware 将在开始处理请求时在您的应用程序上调用一个钩子方法。这个钩子方法允许您的应用程序定义它要使用的 AuthorizationService。将以下方法添加到您的 src/Application.php

public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
    $resolver = new OrmResolver();

    return new AuthorizationService($resolver);
}

OrmResolver 允许授权插件为 ORM 实体和查询找到策略类。其他解析器可用于为其他资源类型查找策略。

接下来,让我们将 AuthorizationComponent 添加到 AppController。在 src/Controller/AppController.php 中,将以下内容添加到 initialize() 方法中

$this->loadComponent('Authorization.Authorization');

最后,我们将通过将以下内容添加到 src/Controller/UsersController.php 中来标记添加、登录和注销操作为不需要授权

// In the add, login, and logout methods
$this->Authorization->skipAuthorization();

skipAuthorization() 方法应在任何即使未登录的用户也可以访问的控制器操作中调用。

创建我们的第一个策略

授权插件将授权和权限建模为策略类。这些类实现逻辑以检查给定 资源 上的 身份 是否被允许 执行操作。我们的 身份 将是已登录的用户,我们的 资源 是我们的 ORM 实体和查询。让我们使用 bake 来生成一个基本的策略

bin/cake bake policy --type entity Article

这将为我们的 Article 实体生成一个空的策略类。您可以在 src/Policy/ArticlePolicy.php 中找到生成的策略。接下来更新策略以看起来像以下内容

<?php
namespace App\Policy;

use App\Model\Entity\Article;
use Authorization\IdentityInterface;

class ArticlePolicy
{
    public function canAdd(IdentityInterface $user, Article $article)
    {
        // All logged in users can create articles.
        return true;
    }

    public function canEdit(IdentityInterface $user, Article $article)
    {
        // logged in users can edit their own articles.
        return $this->isAuthor($user, $article);
    }

    public function canDelete(IdentityInterface $user, Article $article)
    {
        // logged in users can delete their own articles.
        return $this->isAuthor($user, $article);
    }

    protected function isAuthor(IdentityInterface $user, Article $article)
    {
        return $article->user_id === $user->getIdentifier();
    }
}

虽然我们已经定义了一些非常简单的规则,但您可以在策略中使用应用程序所需的复杂逻辑。

在 ArticlesController 中检查授权

创建了我们的策略后,我们可以开始在每个控制器操作中检查授权。如果我们忘记在控制器操作中检查或跳过授权,授权插件将引发异常,让我们知道我们忘记了应用授权。在 src/Controller/ArticlesController.php 中,将以下内容添加到 addeditdelete 方法中

public function add()
{
    $article = $this->Articles->newEmptyEntity();
    $this->Authorization->authorize($article);
    // Rest of the method
}

public function edit($slug)
{
    $article = $this->Articles
        ->findBySlug($slug)
        ->contain('Tags') // load associated Tags
        ->firstOrFail();
    $this->Authorization->authorize($article);
    // Rest of the method.
}

public function delete($slug)
{
    $this->request->allowMethod(['post', 'delete']);

    $article = $this->Articles->findBySlug($slug)->firstOrFail();
    $this->Authorization->authorize($article);
    // Rest of the method.
}

AuthorizationComponent::authorize() 方法将使用当前的控制器操作名称来生成要调用的策略方法。如果您想调用不同的策略方法,您可以使用操作名称调用 authorize

$this->Authorization->authorize($article, 'update');

最后,将以下内容添加到 ArticlesController 上的 tagsviewindex 方法中

// View, index and tags actions are public methods
// and don't require authorization checks.
$this->Authorization->skipAuthorization();

修复添加和编辑操作

虽然我们阻止了对编辑操作的访问,但我们仍然允许用户在编辑期间更改文章的 user_id 属性。我们将在接下来解决这些问题。首先是 add 操作。

创建文章时,我们希望将 user_id 修复为当前登录的用户。用以下内容替换您的添加操作

// in src/Controller/ArticlesController.php

public function add()
{
    $article = $this->Articles->newEmptyEntity();
    $this->Authorization->authorize($article);

    if ($this->request->is('post')) {
        $article = $this->Articles->patchEntity($article, $this->request->getData());

        // Changed: Set the user_id from the current user.
        $article->user_id = $this->request->getAttribute('identity')->getIdentifier();

        if ($this->Articles->save($article)) {
            $this->Flash->success(__('Your article has been saved.'));

            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error(__('Unable to add your article.'));
    }
    $tags = $this->Articles->Tags->find('list')->all();
    $this->set(compact('article', 'tags'));
}

接下来,我们将更新 edit 操作。用以下内容替换编辑方法

// in src/Controller/ArticlesController.php

public function edit($slug)
{
    $article = $this->Articles
        ->findBySlug($slug)
        ->contain('Tags') // load associated Tags
        ->firstOrFail();
    $this->Authorization->authorize($article);

    if ($this->request->is(['post', 'put'])) {
        $this->Articles->patchEntity($article, $this->request->getData(), [
            // Added: Disable modification of user_id.
            'accessibleFields' => ['user_id' => false]
        ]);
        if ($this->Articles->save($article)) {
            $this->Flash->success(__('Your article has been updated.'));

            return $this->redirect(['action' => 'index']);
        }
        $this->Flash->error(__('Unable to update your article.'));
    }
    $tags = $this->Articles->Tags->find('list')->all();
    $this->set(compact('article', 'tags'));
}

在这里,我们通过 patchEntity() 的选项来修改可以进行批量分配的属性。有关更多信息,请参见 更改可访问字段 部分。请记住从 templates/Articles/edit.php 中删除 user_id 控件,因为我们不再需要它。

总结

我们构建了一个简单的 CMS 应用程序,允许用户登录、发布文章、标记文章、按标签浏览发布的文章,并对文章应用基本访问控制。我们还通过利用 FormHelper 和 ORM 功能添加了一些不错的 UX 改进。

感谢您抽出时间探索 CakePHP。接下来,您应该进一步了解 数据库访问和 ORM,或者您可以浏览 使用 CakePHP