Help us understand the problem. What is going on with this article?

Yii2でもかんたんRBAC(ロールベースアクセスコントロール)

More than 5 years have passed since last update.

この記事は Yii 1.1 用に書いた Yiiのアクセスコントロールの簡単な作り方 の Yii2 版です。

YiiにはRBAC(ロールベースアクセスコントロール)と呼ばれる機能が備わっています。これは、ユーザーのロールを階層的に定義して、それぞれのロールに権限を持たせるという、非常に本格的なものです。

http://www.yiiframework.com/doc-2.0/guide-security-authorization.html#role-based-access-control-rbac

使い方としては、1 と比べてずいぶん気軽なメソッド名になりました。

if (!Yii::$app->user->can('manage')) {
    throw new ForbiddenHttpException('アクセスできません');
}

また、Yii 2 になって、AccessControl アクションフィルタもビヘイビア内のオブジェクト構成情報で完結する綺麗なかたちになり、なにかと一貫性が高まってカスタマイズしやすくなりました。

use yii\filters\AccessControl;

class SomeController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                    [
                        'allow' => true,
                        'roles' => ['manage'],
                    ],
                ],
            ],
        ];
    }

が、まだまだ定義が...

使いやすいのはいいけど、機能の開発中にデータベースにロール階層を定義したり、ロールを構成するPHPコードをメンテしたりするのは面倒です。簡単なアプリケーションやプロトタイピング段階では、逆にデメリットになってしまいます。

そこでまたまた、かんたんRBACです。Yii 1 のときは CWebUser をすり替えました。Yii 2 ではもっと手軽にやれます。

RBAC のメカニズムのキーは、Yii::$app->user->can(...) です。このメソッドは Yii::$app->authManager を使う前提で実装されています。

というわけで、Applicationのコンフィグに authManager が必要です。このデフォルトのコンポーネントは勝手に生成されないので、なにか明示的に作る必要があります。

config/web.php をこう変更しましょう。(Application の構成情報のことで、もしかしたら別の名前のファイルかもしれません)

return [
    // ...
    'components' => [
        // ...
        'authManager' => [
            'class' => '\app\AuthManager',
        ],
        // ...
    ],
];

本当なら、 \yii\rbac\ManagerInterface のすべてのメソッドを実装するクラスとして、yii\rbac\DbManageryii\rbac\PhpManager あるいはその互換実装が必要なのですが、このインターフェース、メソッド数がけっこう多い。

実はこの ManagerInterface 、\yii\web\Usercan()checkAccsess メソッドを呼んでいるだけで、他に外から使わているメソッドはひとつもないのです。

public function checkAccess($userId, $permissionName, $params = []);

他のメソッドの大多数は、ユーザーがロール階層構築に利用するために定義されていて、こっちから呼び出さなければ使われることはありません。いくらか呼び出されているメソッドも、内部的にロール階層構築に使う以外の目的では使われていません。

※ 個人的には、ManagerInterface の中から checkAccsess() だけ実装したインターフェースを取り出して、UserManagerInterface ではなくその小さなインターフェースに依存するという設計が、インターフェース分離原則的に適切だったんじゃないかと思います。

と、いうことは... authManager はこんなダックタイプでも動くわけで...

<?php
namespace app;

use yii\base\Component;

class AuthManager extends Component
{
    /**
     * @param string|integer $userId
     * @param string $permissionName
     * @param array $params
     * @return boolean
     * @throws \yii\base\InvalidParamException
     */
    public function checkAccess($userId, $permissionName, $params = [])
    {
        // TODO: なにか実装する
        // TODO: 想定外の $permissionName なら InvalidParamException を投げる
    }
}

じゃあ、どう実装するか

public function checkAccess($userId, $permissionName, $params = [])
{
    if (empty($userId)) {
        return false;
    }
    $dbUser = \app\models\UserActiveRecord::findOne($userId);
    switch ($permissionName) {
        case 'admin':
            return $dbUser->is_admin;
        case 'manage':
            return $dbUser->is_admin || $dbUser->is_manager;
        default:
            throw new \yii\base\InvalidParamException("Unknown role '{$permissionName}'.");
    }
}

ロール2つで済むならこんな感じで完了です。

データベースにアクセスしているので何度も問い合わせられたら困ると思うかもしれませんが、可否を調べた結果は \yii\web\User がしばらく保持します。必要以上に呼ばれることはありません。

これを使い、ユーザーが manage できるかどうか、できるならパス、もしかしたらログインしたらできるかもしれない場合はログインへ、ログインしてるのにダメなら HTTP の 403 エラー確定、という制限はこう書けます。

$user = Yii::$app->user;
if (!$user->can('manage')) {
    if ($user->isGuest) {
        $user->loginRequired();
    } else {
        throw new ForbiddenHttpException('oops');
    }
}

で、こんなのがコントローラーの action1 にも action2 にも書いてある場合、 AccessControl フィルタビヘイビアでまとめるとこうなります。

class SomeController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                    [
                        'actions' => ['action1', 'action2'],
                        'allow' => true,
                        'roles' => ['manage'],
                    ],
                ],
            ],
        ];
    }

おてがるですね。

ダックタイプな実装にすり替えても動くのがミソです。インターフェースを合わせないと将来の変更が心配な人は、yii\rbac\PhpManager あたりのコードを継承しておいてもいいんじゃないでしょうか。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした