CakePHP 1.2のAuthコンポーネントを利用してユーザグループ毎にアクセスコントロールをする方法

標準

Authコンポーネントを利用して、ユーザグループごとに利用できるアクションを割り当てる方法をご紹介します。
※簡易ACLって感じで。

できること

  • ユーザグループごとに各アクションの利用可否を設定
  • アクションの認可情報は、コントローラ内に記述
  • 認証ユーザごとにログイン後の遷移先を変更(おまけ2)

(この方法、どこかの記事を参考にさせていただいたのですが参考元がわからなくなりました。。)

ここで紹介したソースをまとめたものはこちら

GroupテーブルとUserテーブルはいたって普通にこんな感じ。

CREATE  TABLE IF NOT EXISTS `groups` (
  `id` CHAR(36) NOT NULL ,
  `created` DATETIME NULL ,
  `modified` DATETIME NULL ,
  `name` VARCHAR(255) NULL ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci;
CREATE  TABLE IF NOT EXISTS `users` (
  `id` CHAR(36) NOT NULL ,
  `created` DATETIME NULL ,
  `modified` DATETIME NULL ,
  `username` VARCHAR(50) NOT NULL ,
  `password` VARCHAR(40) NOT NULL ,
  `group_id` CHAR(36) NOT NULL ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci;

userモデルは、blongToを付け加えてgroupを引っ張ってこれるようにしておきます。

< ?php
class User extends AppModel {
    var $name = 'User';
	var $belongsTo = array(
		'Group' => array(
			'className' => 'Group',
			'foreignKey' => 'group_id',
			'conditions' => '',
			'fields' => '',
			'order' => ''));
}

app_controller.phpに、beforeFilter、isAuthorizedメソッドを追加します。

< ?php
class AppController extends Controller
{
    public $components = array('Auth');
    /**
     * アクションのアクセスコントロール用
     *   array(
     *     'action' => '*', // すべてのグループで利用可能
     *     'action' => array('group1', 'group2') // group1とgroup2で利用可能
     *   )
     *
     * @var array
     */
    public $permissions = array();
    /**
     * @var AuthComponent
     */
    public $Auth;
    /**
     *
     * @var SessionComponent
     */
    var $Session;
    /**
     * (non-PHPdoc)
     * @see cake/libs/controller/Controller#beforeFilter()
     */
    function beforeFilter()
    {
        // Authコンポーネントの基本設定
        $this->Auth->fields = array('username' => 'username', 'password' => 'password'); // 認証に使うフィールドを指定
        $this->Auth->loginError = __('ログインに失敗しました。IDまたはパスワードが不正です。', true);
        $this->Auth->authError  = __('閲覧権限がありません。', true);
        $this->Auth->authorize = 'controller'; // 権限があるかどうかをAppController::isAuthorized()を使ってチェック
        if($this->isAdmin()){
            // adminルーティングの場合の処理
            $this->layout = 'admin'; // レイアウトを変更
            $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login', 'admin' => 'admin'); // loginアクションを UsersController::admin_login()にする
            $this->Auth->loginRedirect = '/admin/'; // ログイン後のデフォルト遷移先
        }
    }
    /**
     * Check Admin routing
     * @return boolean
     */
    function isAdmin()
    {
        return Configure::read('Routing.admin') && !empty($this->params['admin']);
    }
    /**
     * (non-PHPdoc)
     * @see cake/libs/controller/Controller#isAuthorized()
     */
    function isAuthorized()
    {
        // - group base auth
        $group = $this->Auth->user('group');
        $action = $this->action;
        if (!empty($this->permissions[$action])) {
            $permission = $this->permissions[$this->action];
            if (is_scalar($permission)) {
                $permission = array($permission);
            }
            if ($permission[0] == '*') {
                return true;
            }
            if (in_array($group, $permission)) {
                return true;
            }
        }
        return false;
    }
}

AppController::beforeFilter()ではAuthComponentのパラーメータを調節し、adminルーティングが行われている場合は、管理ページ側レイアウトへ変更するようにしています。

AppController::isAuthorized()では、認証されたユーザのグループと各アクションに割り振られた権限を比較してアクセスコントロールを行います。

各アクションが、どのユーザグループで利用可能かを定義するため、AppController::$permissions変数を追加しています。各コントローラではこの変数をオーバライドして、ユーザグループごとにアクションの利用を許可します。

ログイン処理を行う、UsersControllerはこんな感じ。

< ?php
class UsersController extends AppController {
    var $name = 'Users';
    var $uses = array('User');
    /**
     *
     * @var User
     */
    var $User;
    /**
     * (non-PHPdoc)
     * @see cake/libs/controller/Controller#beforeFilter()
     */
    public function beforeFilter()
    {
        parent::beforeFilter();
        // 認証なしでアクセスできるアクション
        $this->Auth->allow('login', 'admin_login');
        // 表示に権限が必要なアクション
        $this->permissions = array(
            'admin_index' => ACL::$ACL_CLIENT, // 例えば、ユーザ一覧の表示は管理者とクライアントが可能
            'admin_edit' =>  ACL::$ACL_ADMIN // 例えば、ユーザ一覧の編集はは管理者グループのみ可能
        );
        // redirectを抑制
        if (in_array($this->action, array('login', 'admin_login'))) {
            $this->Auth->autoRedirect = false;
        }
    }
    /**
     * コントロールパネル クライアント一覧
     */
    public function admin_index()
    {
        // ごにょごにょ
    }
    /**
     * コントロールパネル クライアント編集
     */
    public function admin_edit()
    {
        // ごにょごにょ
    }
    /**
     * コントロールパネル ログイン処理
     */
    public function admin_login()
    {
        // ログイン処理は共通メソッド
        $this->_login();
    }
    /**
     * コントロールパネル ログアウト処理
     */
    public function admin_logout()
    {
        // ログアウト処理は共通メソッド
        $this->_logout();
        $this->redirect(array('action' => 'login', 'admin' => true));
    }
    /**
     * ログイン処理(一般ユーザ側)
     */
    public function login()
    {
        // ログイン処理は共通メソッド
        $this->_login();
    }
    /**
     * ログアウト処理(一般ユーザ側)
     */
    public function logout()
    {
        $this->_logout();
        $this->redirect(array('action' => 'login'));
    }
    /**
     * ログイン処理(共通)
     */
    protected function _login()
    {
        // ログインしていれば
        if ($this->Auth->user()) {
            // グループを取得
            $group = $this->User->find('first', array('conditions' => array('User.id' => $this->Auth->user('id')), 'recursive' => 0));
            // Authセッションにグループ名を追加
            $this->Session->write($this->Auth->sessionKey . '.group', $group['Group']['name']);
            // 画面を遷移
            $this->redirect($this->Auth->redirect());
        }
    }
    /**
     * ログアウト処理(共通)
     */
    protected function _logout()
    {
        $this->Auth->logout();
        $this->Session->del('Auth.redirect');
        $this->Session->setFlash(__('セッションを終了しました。', true), null, null, 'auth');
    }
}

UsersController::beforeFileter()で、ログインアクションが呼ばれた際に、Authコンポーネントの自動リダイレクト処理をキャンセルするよう指示し、リダイレクト処理はログインアクション中(今回は、UsersController::_login())で行うようにしておきます。

また、通常、Admin共通のログイン処理メソッド、UsersController::_login()を作成してそこで、Authセッションにグループ名を追加する処理を行います。

おまけ:
この方法で、Controller::$permissionsの設定を簡便化するために、config/acl.phpを作成してユーザグループのグループを作っておくとちょっと幸せになるかもしれません。

< ?php
// -- AUTH groups
/**
 * 管理者ユーザのグループ名
 * @var string
 */
define('AUTH_GROUP_ADMIN', 'admin');
/**
 * クライアントユーザーのグループ名
 * @var string
 */
define('AUTH_GROUP_CLIENT', 'client');
/**
 * 一般ユーザーのグループ名
 * @var string
 */
define('AUTH_GROUP_NORMAL', 'normal');
class ACL
{
    /**
     * 全てのユーザグループを許可
     * @var array
     */
    static $ACL_ALL = array('*');
    /**
     * クライアント機能にアクセス可能なユーザグループ
     * (管理者、クライアントユーザともにアクセス可能)
     * @var array
     */
    static $ACL_CLIENT = array(AUTH_GROUP_ADMIN, AUTH_GROUP_CLIENT);
    /**
     * 管理者機能にアクセス可能なユーザグループ
     * (管理者のみアクセス可能)
     * @var array
     */
    static $ACL_ADMIN = array(AUTH_GROUP_ADMIN);
    /**
     * ユーザ側機能にアクセス可能なユーザグループ
     * (一般ユーザのみアクセス可能)
     * @var array
     */
    static $ACL_USER = array(AUTH_GROUP_NORMAL);
}

作成したら、config/bootstrap.phpに以下を追記して読み込み。

require_once('acl.php');

おまけ2:
UsersController::beforeFileter()で、

		// redirectを抑制
        if (in_array($this->action, array('login', 'admin_login'))) {
            $this->Auth->autoRedirect = false;
        }

としておくと、認証時に自動リダイレクトされなくなるので、UsersController::login()でログイン後の遷移先を変更できます。(例えば、グループを見てadminの場合は遷移先を/admin/にするとか。

7 Comments

  1. こんにちは!拝見させていただき、大変ためになりました。
    私の場合はgroupではなく、usersに種別を持たせた形を考えていたのですが、ここも柔軟に対応できる形になっていたので参考にさせていただいています。
    一点、確認なのですが

    ・ユーザー一覧:管理者と、一般ユーザ
    ・ユーザー編集:管理者のみ
    のように権限を設定した場合、一般ユーザが一覧→編集に遷移した際には(リンク続きなので)エラーがでるのですが、まったく違うコントローラーから、URLを時下打ちし、users/edit/5などに遷移した場合にはどのようにするのがベストなのでしょうか。isAuthorized内でfalseで返すタイミングで、どこかのエラーページにリダイレクトするのが望ましいのでしょうか。(現在はそうしています)

    エラーを出す文字列が自画面なので、URLじか打ちで権限に引っかかった場合、その画面でエラーを出力しようとするのでどうも無限ループになっているのです。(私の書き方がまずいのかな・・)今はisAuthorized内でfalse遷移の場合に特定のページにリダイレクトしています。
    isAuthorized以外で「認証に失敗した」事を取得できるタイミングは他にないようなのでここに書くのが正攻法ということでしょうか。

    教えていただき、さらに質問してすみません。気になってしまったので質問させていただきました。回答していただけたらうれしいです。

    • yashioさま >

      通常ですとisAuthorized()からfalseが返ってきた場合、Authコンポーネントは、refererを参照して遷移前のページに戻ります。
      また、Session::setFlash()で、セッションにauthErrorのメッセージを書き込みます。(このセットされたメッセージはビューで、$session->flash(‘auth’);とすれば表示されます。)

      特定のエラーページに遷移させたいということであれば、おっしゃるとおり、isAuthorized内でリダイレクト処理をかけるのがベストでしょうね。(Controller::beforeFilterでセッションのMessage.authをチェックする方法もあります。)

      おすすめだと思うAuthエラー処理は、Controller::flash()メソッドを利用してログイン後のページにリダイレクトでしょうか。

  2. nojimage 様
    お返事ありがとうございます。
    つまり、URLを直接指定して遷移した場合はrefererを参照し、遷移前のURLに戻り、エラーメッセージを出すという事なのですね。
    私の場合は、なぜかFireFoxで「ループしています」というメッセージがでるので、うまく参照できていないきがします。(正常動作だとエラーメッセージをrefererで取得した画面で表示すると推測します)

    ログイン後のページにリダイレクトというのも方法としてありますね。検討してみます。とにかく非常に参考にさせていただきました。ありがとうございました。

  3. Pingback: matya's diary - ユーザグループ管理

  4. こんにちは!!

    cakephpでWebアプリを作成しているものです。
    ACLについて何かいい情報はないかと模索中に当サイトを拝見致しました。
    非常にわかりやすく参考にさせていただきましたが
    1点だけ確認したいことがありましてコメント致しました。

    authコンポーネントのデフォルト利用テーブルのusersから
    他テーブルに変更する際は
    $this->Auth->userModel = ‘モデル名’;
    で変更しますが、この記述をusers_controllerのbeforefilter()に追加したら
    authコンポーネントが使えなくなりました。
    現象としてはわざと違ったID、Passでログインしようとしても
    エラーメッセージが表示されず、authコンポーネントが利用されていないイメージです。

    何か特別な設定方法があるのでしょうか。
    ご返答頂けると幸いです。

    以上

    • $this->Auth->userModel = 'モデル名';

      の記述は、UsersControllerではなく、AppController::beforeFilter()に記述します。

      ただし、この場合上記コードのモデル名やコントローラ名など色々と変更しなければなりませんので、そのあたりもお忘れなく。

      テーブル名だけを変更したいのであれば、以下のようにUser::$useTableを変更するのが1番簡単かと思います。

      class User extends AppModel {
          // .. snip
          var $useTable = 'alter_users';
          // .. snip
      }
  5. nojimage様

    お世話になります。
    ご返答ありがとうございます。

    さっそく試してみたいと思います。
    何か判明次第、ご報告致します。

    以上

コメントを残す

Page optimized by WP Minify WordPress Plugin