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

Yiiのアクセスコントロールの簡単な作り方

More than 5 years have passed since last update.

Yiiには、DBなどの外部データに定義を置けるロールベースのアクセスコントロールの仕組みが搭載されています。

認証と権限付与 6. ロールベースアクセスコントロール

が、これがちょっと回りくどいんですよね。

そこで、もっと簡単なアクセスコントロールを作ってみましょう。

まず、protected/config/main.php の中で、Yii::app()->user のインスタンスをデフォルトの CWebUser から独自のものにすり替えます。

<?php

    // application components
    'components'=>array(
        'user'=>array(
            'class'=>'WebUser', // これ
            // enable cookie-based authentication
            'allowAutoLogin'=>true,
        ),
        // 他のコンポーネントいろいろ
    ),

デフォルトの CWebUser を継承して protected/components/WebUser.php に自分の WebUser を作ります。

CWebUser::isGuest みたいなものをもっといろいろ

<?php
/**
 * Class WebUser
 * 
 * @property bool isAdmin
 */
class WebUser extends CWebUser
{
    /**
     * @return bool
     */
    public function getIsAdmin()
    {
        if ($this->isGuest) {
            return false;
        }
        $dbUser = MyUser::model()->findByAttributes(array(
            'name' => $this->name,
        ));
        return $dbUser && $dbUser->isAdmin;
    }
}

これで、Yii::app()->user->isGuest 以外に、Yii::app()->user->isAdmin なんて問い合わせ方もOKになりました。

この方法なら、自分のモデルに合わせて任意にカスタマイズできます。ちょうど、初期プロジェクトにデフォルトである protected/components/UserIdentity.php のパスワード認証みたいな感じです。あれも、DBのパスワードと照合するように、自分独自の仕様で書き換えますよね。

ということで、これをコントローラの accessRules メソッドの実装で使うとどうなるかというと…

<?php
class MyController extends Controller {

    public function accessRules()
    {
        return array(
            array('allow', // メンバーは表示だけ
                'actions'=>array('index', 'view'),
                'users'=>array('@'),
            ),
            array('allow', // はいここでAdminだけ更新可能っと
                'actions'=>array('create','update','delete'),
                'expression'=>'$user->isAdmin',
            ),
            array('deny',  // ログインしてね
                'users'=>array('*'),
            ),
        );
    }

ユーザ名がズバリ admin じゃないとダメだったのが、任意のロジックでログインユーザが管理者かどうかを聞けるようになりました。一般ユーザが管理者権限を付与されるなんてことも…

IAuthManager::checkAccess() への委譲をやめる

さらにそこから、CWebUser::checkAccess() をオーバーライドして IAuthManager への委譲を奪うと、アプリケーションから見たら高級なロールベースのアクセスコントロールに見えながら、その実は自分のモデル独自の実装で動いているという形にできます。

<?php
/**
 * Class WebUser
 *
 * @property User $dbUser
 * @property bool isAdmin
 * @property bool canAccessMaterialPicture
 * @property bool canAccessFinePicture
 */
class WebUser extends CWebUser
{
    /* @var User */
    private $_dbUser;

    /**
     * @param string $operation
     * @param array $params
     * @param bool $allowCaching
     * @return bool
     */
    public function checkAccess($operation, $params = array(), $allowCaching = true)
    {
        $dbUser = $this->getDbUser($allowCaching);
        // ユーザのモデルインスタンスが operation ができるロールを持っているか
        return $dbUser->hasRoleToDo($operation);
    }

    /**
     * @param bool $allowCaching
     * @throws CException
     * @return User
     */
    public function getDbUser($allowCaching = true)
    {
        if ($allowCaching && isset($this->_dbUser)) {
            return $this->_dbUser;
        } else {
            $user = MyUser::model()->findByAttributes(array(
                'name' => $this->name
            ));
            if (empty($user)) {
                throw new CException("ログインユーザがデータベースに存在しません。");
            }
            if ($allowCaching) {
                $this->_dbUser = $user;
                return $user;
            }
            else {
                unset($this->_dbUser);
            }
            return $user;
        }
    }

    /**
     * @return bool
     */
    public function getIsAdmin()
    {
        // return $this->dbUser->isAdmin;
        return $ths->checkAccess('admin');
    }
}

これで、フルのRBACをするほどのことはない、もっと簡単なロールモデルでもOKになりました。階層的なロールモデルを持っていて、CDbAuthManager でDB上にそのロールの階層構造を構築する、というような方法では設計過剰になるケースも多々あります。いっぽうこの方法では、小さな仕様を MyUser:: hasRoleToDo($operation) あたりにスクラッチで書いて閉じ込めることができます。

使い方は IAuthManager がある場合と同じです。

if (Yii::app()->user->checkAccess('admin')) {
    // 大事なこと
}

ということは…

<?php
class MyController extends Controller {

    public function accessRules()
    {
        return array(
            array('allow', // メンバーは表示だけ
                'actions'=>array('index', 'view'),
                'users'=>array('@'),
            ),
            array('allow', // AdminとWriterだけ更新可能
                'actions'=>array('create','update','delete'),
                'roles'=>array('admin', 'writer'),
            ),
            array('deny',  // ログインしてね
                'users'=>array('*'),
            ),
        );
    }

やった。 expressionroles になりました。

accessRules() から eval コードを追い出せました。勝手なコンポーネントの仕様に依存することもありません。後でフルのRBACに透過的に入れ替えることもできます。めでたしめでたし。

Yiiは、オブジェクト指向的に一貫したルールで、どんなコンポーネントでも勝手に置換できるのがいいですね。

Why do not you register as a user and use Qiita more conveniently?
  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
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