2人のフォロワー

認可

認可とは、ユーザーが何かを行うのに十分な権限を持っているかどうかを確認するプロセスです。Yii は、アクセス制御フィルタ (ACF) とロールベースアクセス制御 (RBAC) の 2 つの認可方法を提供します。

アクセス制御フィルタ

アクセス制御フィルタ (ACF) は、yii\filters\AccessControl として実装された単純な認可方法であり、単純なアクセス制御のみを必要とするアプリケーションで最適に使用されます。名前が示すように、ACF は、コントローラーまたはモジュールで使用できるアクションフィルタです。ユーザーがアクションの実行を要求すると、ACF はアクセスのルールのリストをチェックして、ユーザーが要求されたアクションにアクセスできるかどうかを判断します。

以下のコードは、`site` コントローラーで ACF を使用する方法を示しています。

use yii\web\Controller;
use yii\filters\AccessControl;

class SiteController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::class,
                'only' => ['login', 'logout', 'signup'],
                'rules' => [
                    [
                        'allow' => true,
                        'actions' => ['login', 'signup'],
                        'roles' => ['?'],
                    ],
                    [
                        'allow' => true,
                        'actions' => ['logout'],
                        'roles' => ['@'],
                    ],
                ],
            ],
        ];
    }
    // ...
}

上記のコードでは、ACFがsiteコントローラにビヘイビアとしてアタッチされています。これはアクションフィルタを使用する一般的な方法です。onlyオプションは、ACFがloginlogoutsignupアクションのみに適用されることを指定します。siteコントローラのその他すべてのアクションは、アクセス制御の対象外となります。rulesオプションには、アクセスルールがリストされており、以下のように読み取られます。

  • ゲストユーザー(まだ認証されていないユーザー)全員がloginsignupアクションにアクセスすることを許可します。rolesオプションには、疑問符?が含まれており、これは「ゲストユーザー」を表す特別なトークンです。
  • 認証済みユーザーがlogoutアクションにアクセスすることを許可します。@文字は、「認証済みユーザー」を表す別の特別なトークンです。

ACFは、上から下にアクセスルールを1つずつ調べて、現在の実行コンテキストに一致するルールが見つかるまで、認可チェックを実行します。一致するルールのallow値を使用して、ユーザーが認可されているかどうかを判断します。どのルールにも一致しない場合、ユーザーは認可されておらず、ACFはそれ以上のアクション実行を停止します。

ACFがユーザーが現在のアクションにアクセスすることを認可されていないと判断した場合、デフォルトで以下の措置を取ります。

以下の例のように、yii\filters\AccessControl::$denyCallbackプロパティを構成することで、この動作をカスタマイズできます。

[
    'class' => AccessControl::class,
    ...
    'denyCallback' => function ($rule, $action) {
        throw new \Exception('You are not allowed to access this page');
    }
]

アクセスルールは多くのオプションをサポートしています。以下は、サポートされているオプションの概要です。yii\filters\AccessRuleを拡張して、独自のアクセスルールクラスを作成することもできます。

  • allow:これが「許可」ルールか「拒否」ルールかを指定します。

  • actions:このルールが一致するアクションを指定します。これはアクションIDの配列である必要があります。比較は大文字と小文字が区別されます。このオプションが空の場合、または設定されていない場合、ルールはすべてのアクションに適用されます。

  • controllers:このルールが一致するコントローラを指定します。これはコントローラIDの配列である必要があります。各コントローラIDには、モジュールID(存在する場合)が接頭辞として付加されます。比較は大文字と小文字が区別されます。このオプションが空の場合、または設定されていない場合、ルールはすべてのコントローラに適用されます。

  • roles:このルールが一致するユーザーロールを指定します。yii\web\User::$isGuestを介してチェックされる2つの特別なロールが認識されます。

    • ?:ゲストユーザー(まだ認証されていない)に一致します。
    • @:認証済みユーザーに一致します。

    他のロール名を使用すると、yii\web\User::can()の呼び出しがトリガーされます。これはRBACの有効化が必要です(次のセクションで説明します)。このオプションが空の場合、または設定されていない場合、このルールはすべてのロールに適用されます。

  • roleParamsyii\web\User::can()に渡されるパラメータを指定します。RBACルールの説明にあるセクションを参照して、使用方法を確認してください。このオプションが空の場合、または設定されていない場合、パラメータは渡されません。

  • ips:このルールが一致するクライアントIPアドレスを指定します。IPアドレスには、末尾にワイルドカード*を含めることができるため、同じプレフィックスを持つIPアドレスと一致します。「192.168.*」は、「192.168.」セグメント内のすべてのIPアドレスと一致します。このオプションが空の場合、または設定されていない場合、このルールはすべてのIPアドレスに適用されます。

  • verbs:このルールが一致するリクエストメソッド(例:GETPOST)を指定します。比較は大文字と小文字が区別されません。

  • matchCallback:このルールを適用するかどうかを判断するために呼び出されるPHPコールバックを指定します。

  • denyCallback:このルールがアクセスを拒否する場合に呼び出されるPHPコールバックを指定します。

以下は、任意のアクセスチェックロジックを作成できるmatchCallbackオプションの使用方法を示す例です。

use yii\filters\AccessControl;

class SiteController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::class,
                'only' => ['special-callback'],
                'rules' => [
                    [
                        'actions' => ['special-callback'],
                        'allow' => true,
                        'matchCallback' => function ($rule, $action) {
                            return date('d-m') === '31-10';
                        }
                    ],
                ],
            ],
        ];
    }

    // Match callback called! This page can be accessed only each October 31st
    public function actionSpecialCallback()
    {
        return $this->render('happy-halloween');
    }
}

ロールベースのアクセス制御 (RBAC)

ロールベースのアクセス制御(RBAC)は、シンプルながらも強力な中央集権的なアクセス制御を提供します。RBACと他のより従来のアクセス制御スキームの比較の詳細については、Wikipediaを参照してください。

Yiiは、NIST RBACモデルに従う、一般階層型RBACを実装しています。authManager アプリケーションコンポーネントを介してRBAC機能を提供します。

RBACの使用には、2つの作業が含まれます。最初の部分はRBAC認可データの構築であり、2番目の部分は、必要な場所でアクセスチェックを実行するために認可データを使用することです。

次に説明を容易にするために、最初にいくつかの基本的なRBACの概念を紹介します。

基本概念

ロールは、権限(例:投稿の作成、投稿の更新)のコレクションを表します。ロールは1人以上のユーザーに割り当てることができます。ユーザーが指定された権限を持っているかどうかを確認するには、ユーザーがその権限を含むロールに割り当てられているかどうかを確認できます。

各ロールまたは権限には、ルールを関連付けることができます。ルールは、アクセスチェック中に実行されて、対応するロールまたは権限が現在のユーザーに適用されるかどうかを判断するコードを表します。たとえば、「投稿の更新」権限には、現在のユーザーが投稿の作成者かどうかを確認するルールを含めることができます。アクセスチェック中に、ユーザーが投稿の作成者ではない場合、そのユーザーは「投稿の更新」権限を持っていないと見なされます。

ロールと権限の両方を階層的に編成できます。特に、ロールは他のロールまたは権限で構成できます。また、権限は他の権限で構成できます。Yiiは、より特殊なツリー階層を含む部分順序階層を実装しています。ロールが権限を含むことはできますが、その逆は必ずしもtrueではありません。

RBACの構成

認可データの定義とアクセスチェックの実行を開始する前に、authManagerアプリケーションコンポーネントを構成する必要があります。Yiiは、yii\rbac\PhpManageryii\rbac\DbManagerの2種類の認可マネージャーを提供します。前者はPHPスクリプトファイルを使用して認可データを格納し、後者はデータベースに認可データを格納します。アプリケーションで非常に動的なロールと権限の管理が必要ない場合は、前者を使用することを検討できます。

PhpManagerの使用

次のコードは、yii\rbac\PhpManagerクラスを使用して、アプリケーション構成でauthManagerを構成する方法を示しています。

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\PhpManager',
        ],
        // ...
    ],
];

authManagerには、\Yii::$app->authManagerを介してアクセスできます。

yii\rbac\PhpManagerは、デフォルトで@app/rbacディレクトリ以下のファイルにRBACデータを格納します。権限階層をオンラインで変更する必要がある場合は、ディレクトリとその中のすべてのファイルがWebサーバープロセスによって書き込み可能であることを確認してください。

DbManagerの使用

次のコードは、yii\rbac\DbManagerクラスを使用して、アプリケーション構成でauthManagerを構成する方法を示しています。

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\DbManager',
            // uncomment if you want to cache RBAC items hierarchy
            // 'cache' => 'cache',
        ],
        // ...
    ],
];

注:yii2-basic-appテンプレートを使用している場合、authManagerconfig/web.phpに追加してconfig/console.php構成ファイルに宣言する必要があります。yii2-advanced-appの場合、authManagercommon/config/main.phpに1回だけ宣言する必要があります。

DbManagerは、データを格納するために4つのデータベーステーブルを使用します。

  • itemTable:認可アイテムを格納するためのテーブル。デフォルトは「auth_item」です。
  • itemChildTable:認可アイテム階層を格納するためのテーブル。デフォルトは「auth_item_child」です。
  • assignmentTable:認可アイテムの割り当てを格納するためのテーブル。デフォルトは「auth_assignment」です。
  • ruleTable:ルールを格納するためのテーブル。デフォルトは「auth_rule」です。

先に進む前に、データベースにこれらのテーブルを作成する必要があります。これを行うには、@yii/rbac/migrationsに格納されているマイグレーションを使用できます。

yii migrate --migrationPath=@yii/rbac/migrations

分離されたマイグレーションセクションで、異なる名前空間のマイグレーションの使用方法の詳細をご覧ください。

authManagerには、\Yii::$app->authManagerを介してアクセスできます。

認可データの構築

認可データの構築は、次のタスクすべてに関するものです。

  • ロールと権限の定義。
  • ロールと権限間の関係の確立。
  • ルールの定義。
  • ロールと権限へのルールの関連付け。
  • ユーザーへのロールの割り当て。

認可の柔軟性の要件に応じて、上記のタスクはさまざまな方法で実行できます。権限階層を開発者のみが変更できるようにする場合は、マイグレーションまたはコンソールコマンドのいずれかを使用できます。マイグレーションの長所は、他のマイグレーションと一緒に実行できることです。コンソールコマンドの長所は、複数のマイグレーションに分散されるのではなく、コード内で階層の概要を把握できることです。

いずれの場合も、最終的には次のRBAC階層が得られます。

Simple RBAC hierarchy

権限階層を動的に形成する必要がある場合は、UIまたはコンソールコマンドが必要です。階層自体を構築するために使用されるAPIは変わりません。

マイグレーションの使用

マイグレーションを使用して、authManagerによって提供されるAPIを介して階層を初期化および変更できます。

./yii migrate/create init_rbacを使用して新しいマイグレーションを作成し、階層の作成を実装します。

<?php
use yii\db\Migration;

class m170124_084304_init_rbac extends Migration
{
    public function up()
    {
        $auth = Yii::$app->authManager;

        // add "createPost" permission
        $createPost = $auth->createPermission('createPost');
        $createPost->description = 'Create a post';
        $auth->add($createPost);

        // add "updatePost" permission
        $updatePost = $auth->createPermission('updatePost');
        $updatePost->description = 'Update post';
        $auth->add($updatePost);

        // add "author" role and give this role the "createPost" permission
        $author = $auth->createRole('author');
        $auth->add($author);
        $auth->addChild($author, $createPost);

        // add "admin" role and give this role the "updatePost" permission
        // as well as the permissions of the "author" role
        $admin = $auth->createRole('admin');
        $auth->add($admin);
        $auth->addChild($admin, $updatePost);
        $auth->addChild($admin, $author);

        // Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId()
        // usually implemented in your User model.
        $auth->assign($author, 2);
        $auth->assign($admin, 1);
    }
    
    public function down()
    {
        $auth = Yii::$app->authManager;

        $auth->removeAll();
    }
}

特定のロールを持つユーザーをハードコードしたくない場合は、マイグレーションに->assign()呼び出しを入れないでください。代わりに、割り当てを管理するためのUIまたはコンソールコマンドを作成します。

マイグレーションは、yii migrateを使用して適用できます。

コンソールコマンドの使用

権限階層がまったく変更されず、ユーザー数が固定されている場合は、authManagerによって提供されるAPIを介して一度だけ認可データを初期化する-コンソールコマンドを作成できます。

<?php
namespace app\commands;

use Yii;
use yii\console\Controller;

class RbacController extends Controller
{
    public function actionInit()
    {
        $auth = Yii::$app->authManager;
        $auth->removeAll();
        
        // add "createPost" permission
        $createPost = $auth->createPermission('createPost');
        $createPost->description = 'Create a post';
        $auth->add($createPost);

        // add "updatePost" permission
        $updatePost = $auth->createPermission('updatePost');
        $updatePost->description = 'Update post';
        $auth->add($updatePost);

        // add "author" role and give this role the "createPost" permission
        $author = $auth->createRole('author');
        $auth->add($author);
        $auth->addChild($author, $createPost);

        // add "admin" role and give this role the "updatePost" permission
        // as well as the permissions of the "author" role
        $admin = $auth->createRole('admin');
        $auth->add($admin);
        $auth->addChild($admin, $updatePost);
        $auth->addChild($admin, $author);

        // Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId()
        // usually implemented in your User model.
        $auth->assign($author, 2);
        $auth->assign($admin, 1);
    }
}

注記: 高度なテンプレートを使用している場合は、RbacControllerconsole/controllersディレクトリに配置し、名前空間をconsole\controllersに変更する必要があります。

上記の命令は、次のようにコンソールから実行できます。

yii rbac/init

特定のロールを持つユーザーをハードコードしたくない場合は、コマンドに->assign()呼び出しを入れないでください。代わりに、割り当てを管理するためのUIまたはコンソールコマンドを作成してください。

ユーザーへのロールの割り当て

作成者は投稿を作成でき、管理者は投稿を更新し、作成者と同じすべての操作を実行できます。

アプリケーションでユーザー登録を許可している場合は、これらの新規ユーザーに一度ロールを割り当てる必要があります。たとえば、高度なプロジェクトテンプレートで登録されたすべてのユーザーをオーサーにするには、frontend\models\SignupForm::signup()を次のように変更する必要があります。

public function signup()
{
    if ($this->validate()) {
        $user = new User();
        $user->username = $this->username;
        $user->email = $this->email;
        $user->setPassword($this->password);
        $user->generateAuthKey();
        $user->save(false);

        // the following three lines were added:
        $auth = \Yii::$app->authManager;
        $authorRole = $auth->getRole('author');
        $auth->assign($authorRole, $user->getId());

        return $user;
    }

    return null;
}

動的に更新される承認データを持つ複雑なアクセス制御を必要とするアプリケーションでは、authManagerが提供するAPIを使用して、特別なユーザーインターフェース(管理パネルなど)を開発する必要がある場合があります。

ルールの使用

前述のように、ルールはロールと権限に追加の制約を追加します。ルールは、yii\rbac\Ruleから拡張するクラスです。execute()メソッドを実装する必要があります。以前作成した階層では、作成者は自分の投稿を編集できません。これを修正しましょう。まず、ユーザーが投稿の作成者であることを検証するためのルールが必要です。

namespace app\rbac;

use yii\rbac\Rule;
use app\models\Post;

/**
 * Checks if authorID matches user passed via params
 */
class AuthorRule extends Rule
{
    public $name = 'isAuthor';

    /**
     * @param string|int $user the user ID.
     * @param Item $item the role or permission that this rule is associated with
     * @param array $params parameters passed to ManagerInterface::checkAccess().
     * @return bool a value indicating whether the rule permits the role or permission it is associated with.
     */
    public function execute($user, $item, $params)
    {
        return isset($params['post']) ? $params['post']->createdBy == $user : false;
    }
}

上記のルールは、post$userによって作成されたかどうかを確認します。前に使用したコマンドで、特別な権限updateOwnPostを作成します。

$auth = Yii::$app->authManager;

// add the rule
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);

// add the "updateOwnPost" permission and associate the rule with it.
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);

// "updateOwnPost" will be used from "updatePost"
$auth->addChild($updateOwnPost, $updatePost);

// allow "author" to update their own posts
$auth->addChild($author, $updateOwnPost);

これで、次の階層ができました。

RBAC hierarchy with a rule

アクセスチェック

承認データの準備ができたら、アクセスチェックはyii\rbac\ManagerInterface::checkAccess()メソッドを呼び出すほど簡単です。ほとんどのアクセスチェックは現在のユーザーに関するものであるため、Yiiは便宜上、ショートカットメソッドyii\web\User::can()を提供しており、次のように使用できます。

if (\Yii::$app->user->can('createPost')) {
    // create post
}

現在のユーザーがID=1のJaneの場合、createPostから開始してJaneにアクセスしようとします。

Access check

ユーザーが投稿を更新できるかどうかを確認するには、前に説明したAuthorRuleに必要な追加パラメーターを渡す必要があります。

if (\Yii::$app->user->can('updatePost', ['post' => $post])) {
    // update post
}

現在のユーザーがJohnの場合の動作を示します。

Access check

updatePostから開始し、updateOwnPostを通過します。アクセスチェックに合格するには、AuthorRuleexecute()メソッドがtrueを返す必要があります。このメソッドはcan()メソッド呼び出しから$paramsを受け取るため、値は['post' => $post]になります。すべて正常であれば、Johnに割り当てられているauthorに到達します。

Janeの場合は、管理者であるため少し簡単です。

Access check

コントローラー内では、承認を実装する方法はいくつかあります。追加と削除へのアクセスを分離する細かい権限が必要な場合は、各アクションのアクセスをチェックする必要があります。各アクションメソッドで上記の状態を使用するか、yii\filters\AccessControlを使用できます。

public function behaviors()
{
    return [
        'access' => [
            'class' => AccessControl::class,
            'rules' => [
                [
                    'allow' => true,
                    'actions' => ['index'],
                    'roles' => ['managePost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['view'],
                    'roles' => ['viewPost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['create'],
                    'roles' => ['createPost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['update'],
                    'roles' => ['updatePost'],
                ],
                [
                    'allow' => true,
                    'actions' => ['delete'],
                    'roles' => ['deletePost'],
                ],
            ],
        ],
    ];
}

すべてのCRUD操作をまとめて管理する場合は、managePostなどの単一の権限を使用し、yii\web\Controller::beforeAction()でチェックすることをお勧めします。

上記の例では、アクションへのアクセスに指定されたロールにはパラメーターは渡されませんが、updatePost権限の場合、正しく機能させるためにpostパラメーターを渡す必要があります。yii\web\User::can()にパラメーターを渡すには、アクセスのルールでroleParamsを指定します。

[
    'allow' => true,
    'actions' => ['update'],
    'roles' => ['updatePost'],
    'roleParams' => function() {
        return ['post' => Post::findOne(['id' => Yii::$app->request->get('id')])];
    },
],

上記の例では、roleParamsは、アクセスのルールをチェックするときに評価されるクロージャであるため、モデルは必要な場合にのみロードされます。ロールパラメーターの作成が単純な操作の場合は、次のように配列を指定することもできます。

[
    'allow' => true,
    'actions' => ['update'],
    'roles' => ['updatePost'],
    'roleParams' => ['postId' => Yii::$app->request->get('id')],
],

デフォルトロールの使用

デフォルトロールとは、暗黙的にすべてのユーザーに割り当てられるロールです。yii\rbac\ManagerInterface::assign()を呼び出す必要はなく、承認データにはその割り当て情報は含まれていません。

デフォルトロールは通常、ロールがチェック対象のユーザーに適用されるかどうかを決定するルールに関連付けられています。

デフォルトロールは、すでに何らかのロール割り当てがあるアプリケーションでよく使用されます。たとえば、アプリケーションのユーザーテーブルに「グループ」列があり、各ユーザーが属する権限グループを表している場合があります。各権限グループをRBACロールにマッピングできる場合、デフォルトロール機能を使用して、各ユーザーにRBACロールを自動的に割り当てることができます。例を使用して、これを行う方法を示します。

ユーザーテーブルに、1は管理者グループ、2はオーサーグループを表すgroup列があるとします。これらの2つのグループの権限を表す2つのRBACロールadminauthorを作成する予定です。RBACデータは、まずクラスを作成して設定します。

namespace app\rbac;

use Yii;
use yii\rbac\Rule;

/**
 * Checks if user group matches
 */
class UserGroupRule extends Rule
{
    public $name = 'userGroup';

    public function execute($user, $item, $params)
    {
        if (!Yii::$app->user->isGuest) {
            $group = Yii::$app->user->identity->group;
            if ($item->name === 'admin') {
                return $group == 1;
            } elseif ($item->name === 'author') {
                return $group == 1 || $group == 2;
            }
        }
        return false;
    }
}

次に、前のセクションで説明したように、独自のcommand/migrationを作成します。

$auth = Yii::$app->authManager;

$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);

$author = $auth->createRole('author');
$author->ruleName = $rule->name;
$auth->add($author);
// ... add permissions as children of $author ...

$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $author);
// ... add permissions as children of $admin ...

上記では、「author」が「admin」の子として追加されているため、ルールのクラスのexecute()メソッドを実装する際には、この階層も尊重する必要があります。そのため、ロール名が「author」の場合、ユーザーグループが1または2(「admin」グループまたは「author」グループのいずれかに属していることを意味する)であれば、execute()メソッドはtrueを返します。

次に、yii\rbac\BaseManager::$defaultRolesに2つのロールをリストすることによってauthManagerを構成します。

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\PhpManager',
            'defaultRoles' => ['admin', 'author'],
        ],
        // ...
    ],
];

これで、アクセスチェックを実行すると、それに関連付けられたルールを評価することで、adminauthorの両方のロールがチェックされます。ルールがtrueを返すと、ロールが現在のユーザーに適用されることを意味します。上記のルールの実装に基づいて、これは、ユーザーのgroup値が1の場合、adminロールがユーザーに適用され、group値が2の場合、authorロールが適用されることを意味します。

タイプミスを見つけましたか?または、このページの改善が必要だと考えますか?
githubで編集する !