现在用户可以登录我们的 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();
}
}
虽然我们已经定义了一些非常简单的规则,但您可以在策略中使用应用程序所需的复杂逻辑。
创建了我们的策略后,我们可以开始在每个控制器操作中检查授权。如果我们忘记在控制器操作中检查或跳过授权,授权插件将引发异常,让我们知道我们忘记了应用授权。在 src/Controller/ArticlesController.php 中,将以下内容添加到 add
、edit
和 delete
方法中
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
上的 tags
、view
和 index
方法中
// 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。