CakePHP 4 で Authorization プラグインを用いてアクセス権限を設定する方法
はじめに
今日は CakePHP 4 で Authorization プラグインを用いた、ユーザーのアクセス権限を設定する方法をご紹介します。
- CakePHP
- 4.1.2
- CakePHP Authorization
- 2.0.0
- PHP
- 7.4.5
- MariaDB
- 10.4.11
1. 下準備
今回はサンプルプログラムとして、ブログを想定して、記事の一覧、編集、削除の機能を用意しました。
あくまで動作確認用なので、編集と削除は Flash メッセージの表示だけにしています。
ログイン機能は Authentication プラグインを使って実装しました。
コードは割愛しますが、過去の投稿「CakePHP 4 で CakePHP Authentication プラグインを使ってユーザー認証を実装」などを参考に実装して頂ければと思います。
CREATE TABLE articles (
id int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
user_id int(11) NOT NULL,
title varchar(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE users (
id int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
job varchar(255) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO articles (id, user_id, title) VALUES
(1, 3, 'King の投稿'),
(2, 4, 'Queen の投稿');
-- パスワードは全て 「p@ssw0rd」です
INSERT INTO users (id, username, password, job) VALUES
(1, 'Joker', '$2y$10$2NJl7BcETFs9QHjjy91kJ.pGiJe1JdF8BX9jvIrNTUAEOOH.HiRrW', 'admin'),
(2, 'Ace', '$2y$10$2NJl7BcETFs9QHjjy91kJ.pGiJe1JdF8BX9jvIrNTUAEOOH.HiRrW', 'manager'),
(3, 'King', '$2y$10$2NJl7BcETFs9QHjjy91kJ.pGiJe1JdF8BX9jvIrNTUAEOOH.HiRrW', 'author'),
(4, 'Queen', '$2y$10$2NJl7BcETFs9QHjjy91kJ.pGiJe1JdF8BX9jvIrNTUAEOOH.HiRrW', 'author');
<?php
declare(strict_types=1);
namespace App\Controller;
class ArticlesController extends AppController
{
public function delete($id)
{
$article = $this->Articles->get($id);
$this->Flash->success("ID: {$id} を削除しました");
return $this->redirect(['action' => 'index']);
}
public function edit($id)
{
$article = $this->Articles->get($id);
$this->Flash->success("ID: {$id} を更新しました");
return $this->redirect(['action' => 'index']);
}
public function index()
{
$articles = $this->Articles->find()
->contain('Users')
->all();
$this->set(compact('articles'));
}
}
<?php
declare(strict_types=1);
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Article extends Entity
{
protected $_accessible = [
'*' => true
];
}
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
]);
}
}
<table>
<?php foreach ($articles as $article): ?>
<tr>
<td><?= h($article->id) ?></td>
<td><?= h($article->user->username) ?></td>
<td><?= h($article->title) ?></td>
<td class="actions">
<?= $this->Html->link('Edit', ['action' => 'edit', $article->id]) ?>
<?= $this->Html->link('Delete', ['action' => 'delete', $article->id]) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
2. Authorization プラグインのインストール
composer で cakephp/authorization プラグインをインストールし、
使用するためのコードを Application.php と AppController.php に追加します。
> cd \path\to\cakephp4
> composer require cakephp/authorization:^2.0
- Quick Start (CakePHP Authorization 2.x Cookbook)
- https://book.cakephp.org/authorization/2/en/index.html#quick-start
<?php
...
// ↓ 下記を追加
use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Policy\OrmResolver;
use Psr\Http\Message\ResponseInterface;
// ↓ もしなければ追加
use Psr\Http\Message\ServerRequestInterface;
...
// ↓ implements に AuthorizationServiceProviderInterface を追加
class Application extends BaseApplication implements
AuthenticationServiceProviderInterface, AuthorizationServiceProviderInterface
{
...
public function bootstrap(): void
{
...
$this->addPlugin('Authorization'); // ← 追加
}
...
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
...
// ↓これを追加(直前の ->add(~); のセミコロン削除を忘れずに)
->add(new AuthorizationMiddleware($this));
return $middlewareQueue;
}
...
// ↓ これを追加
public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
$resolver = new OrmResolver();
return new AuthorizationService($resolver);
}
}
public function initialize(): void
{
...
// ▼これを追加
$this->loadComponent('Authorization.Authorization');
}
3. 特定アクション(コントローラ)のアクセス許可
前述のように Authorization を導入すると、各画面に認可処理を入れないとエラーになってしまいます。
ログインやログアウトのように、任意のアクションに対して全ユーザーへのアクセスを許可する場合は、skipAuthorization() を使うことができます。
beforeFilter 内で条件を付けずに skipAuthorization() を実行すれば、コントローラ全体で認可不要にできますね
public function beforeFilter(\Cake\Event\EventInterface $event)
{
...
// ↓ 下記を追加
if (in_array($this->request->getParam('action'), ['login', 'logout'])) {
$this->Authorization->skipAuthorization();
}
}
記事一覧も全ユーザーがアクセス可能なので、認可不要にします。先の例では beforeFilter に記述しましたが、直接アクションに書いても差支えありません。
public function index()
{
// ↓ 追加
$this->Authorization->skipAuthorization();
...
}
- Skipping Authorization (CakePHP Authorization 2.x Cookbook)
- https://book.cakephp.org/authorization/2/en/component.html#skipping-authorization
4. エンティティの内容でアクセス可否
例えば「ログインユーザーが投稿者である場合だけ許可」などのように、ユーザー情報とエンティティの内容によってアクセス可否を設定する場合は、Policy を使うことができます。
試しに、記事の編集と削除は投稿者のみが行えるように実装してみました。
<?php
declare(strict_types=1);
namespace App\Policy;
use App\Model\Entity\Article;
use Authorization\IdentityInterface;
class ArticlePolicy
{
public function canDelete(IdentityInterface $user, Article $article)
{
return $user->id === $article->user_id;
}
public function canUpdate(IdentityInterface $user, Article $article)
{
return $user->id === $article->user_id;
}
}
public function delete($id)
{
$article = $this->Articles->get($id);
// ↓ 追加
$this->Authorization->authorize($article, 'delete');
...
}
public function edit($id)
{
$article = $this->Articles->get($id);
// ↓ 追加
$this->Authorization->authorize($article, 'update');
...
}
先の「1. 下準備」でご紹介したデータベースを使用されている場合は、King や Queen でログインをして、それぞれの投稿でのみ「Edit」と「Delete」で Flash メッセージが表示されることを確認してください。
なお Policy ファイルも Bake で生成することができます。
bin/cake bake policy --type entity Article
- Creating Policies (CakePHP Authorization 2.x Cookbook)
- https://book.cakephp.org/authorization/2/en/policies.html#creating-policies
5. 応用: ユーザー権限とエンティティの内容で判定
users.job が manager のユーザーは、全ユーザーの投稿を編集できるようにしてみます。
/src/Model/Entity/User.php と /src/Model/Table/UsersTable.php がない方は、それらを bake などで作った後に一度ログアウトして、再度ログインしてください。
public function canUpdate(IdentityInterface $user, Article $article)
{
// ↓ $user->is_manager の条件を追加
return ($user->is_manager || $user->id === $article->user_id);
}
// ↓ これを追加
protected function _getIsManager()
{
return $this->job === 'manager';
}
次に管理者アカウントには、Article エンティティに関する全操作を許可します。
これは before() を使うことで、簡単に実装できます。
<?php
...
// ↓ 追加
use Authorization\Policy\BeforePolicyInterface;
// ↓「implements BeforePolicyInterface」を追加
class ArticlePolicy implements BeforePolicyInterface
{
// ↓ これを追加
public function before($user, $resource, $action)
{
if ($user->is_admin) {
return true;
}
}
...
}
// ↓ これを追加
protected function _getIsAdmin()
{
return $this->job === 'admin';
}
- Policy Pre-conditions (CakePHP Authorization 2.x Cookbook)
- https://book.cakephp.org/authorization/2/en/policies.html#policy-pre-conditions
6. 基本的にはすべて許可とする
基本的には全ページを「許可」とするためには Application.php に追記した AuthorizationMiddleware で 'requireAuthorizationCheck' => false を指定します。
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
...
// ↓ 第2引数を追加
->add(new AuthorizationMiddleware($this, [
'requireAuthorizationCheck' => false
]));
7. おわりに
Authorization プラグインについても公式ドキュメントに詳しく説明が書いてあるのですが、CakePHP と違って日本語版が提供されておらず、読むのがちょっと大変かもしれませんね。
僕自身、最初読んだときはイメージがつかめなかったのですが、試しながら何度も読み返すうちに、徐々に分かってきました。
今回紹介しきれなかった機能もありますので、是非公式ドキュメントも目を通していただければと思っています。
- Quick Start (CakePHP Authorization 2.x Cookbook)
- https://book.cakephp.org/authorization/2/en/index.html