【CakePHP 2.x】複数モデルでAuthコンポーネントを使う

Authコンポーネントを使うと、認証機能が簡単に実装できるが、
モデルを運営者モデルと利用者モデルで分けたいことがある。

これを実現するには、AdminRouting を利用する。

運営者モデルをOwnerとして、全ActionをAdminRoutingで実装する。
利用者モデルをUserとして、普通のルーティングで実装する。

運営者のログイン画面は /admin/users/login になり、
利用者のログイン画面は /users/login になる。

最後まで読んで実装すればログインURLはどうとでもなるので、
今回は上記で実装する。

まずは、利用者のログイン機能を実装する。

AppControllerにAuthコンポーネントを定義する。

<?php
class AppController extends Controller {
	public $helpers = array('Html', 'Form', 'Session');
	public $components = array(
		'Session', 
		'Auth' => array(
			'loginAction' => array('controller' => 'users','action' => 'login'), //ログインを行なうaction
                       'loginRedirect' => array('controller' => 'users', 'action' => 'index'), //ログイン後のページ
                       'logoutRedirect' => array('controller' => 'users', 'action' => 'login'), //ログアウト後のページ
			'authError'=>'ログインして下さい。',
			'authenticate' => array(
            	            'Form' => array(
                	        'userModel' => 'User', //ユーザー情報のモデル
                               'fields' => array('username' => 'name','password'=>'password')
                            )
                       )
		)
	);
}


UsersControllerにログインとログアウトのActionを実装する。

<?php
class UsersController extends AppController {
	public $name = 'Users';
	public $uses = array('User', 'Skill');
	public function beforeFilter(){
		$this->Auth->allow('login');
	}
	public function index(){

	}
	public function login(){
		if ($this->request->is('post')) {
			if($this->Auth->login()) {
				//ログイン後のURLが不安定なため、普通に書く。
				$this->redirect(array(
                                         'controller'=>$this->Auth->loginRedirect['controller'], 
                                         'action'=>$this->Auth->loginRedirect['action'])
                                );
                         }else{
                             $this->Session->setFlash(__('ID or pass が正しくありません!'), 'default', array(), 'auth');
                         }
                }
	}
	public function logout(){
		$this->redirect($this->Auth->logout());
	}
	public function add() {
        if ($this->request->is('post')) {

			//created_date の追加
			$this->request->data['User']['created_date'] = date('Y-m-d H:i:s');

            $this->User->create();
            if ($this->User->save($this->request->data)) {
                $this->Session->setFlash(__('The user has been saved'));
                $this->redirect(array('action' => 'login'));
            } else {
                $this->Session->setFlash(__('The user could not be saved. Please, try again.'));
            }
        }
    }

}


CakePHP2.x から パスワードが自動的に暗号化されなくなったので、
モデルに暗号化処理を実装する。

<?php
class User extends AppModel {
	//パスワード暗号化
	//AuthComponent::password()を使えばいいので、 beforeSave() でやる必要もないかも・・・。
	public function beforeSave($options = array()) {
    	    if (isset($this->data[$this->alias]['password'])) {
        	$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password']);
    	    }
    	    return true;
	}
}


とりあえず、これでUserモデルの実装は終わり。
ここまでは他のサイトにも載っているので、
そっちを参考にしてもいい。

ここからOwnerモデルの実装に入る。

まずは AdminRouting を有効にする。
core.phpの以下がコメントアウトされているので、外す。
Configure::write('Routing.prefixes', array('admin'));

次にAppControllerにbeforeFilterを追加する。
このbeforeFilterでAdminRoutingを判定し、
AdminRoutingであればAuthコンポーネントを動的にOwnerモデルのものに差し替える。
「loginAction」「loginRedirect」「logoutRedirect」はAdminRoutingに対応させるため、
「admin=>true」を指定する。
「AuthComponent::$sessionKey = "Auth.Owner"」はセッション名を利用者と異なるものに設定している。
これでUserモデルの認証ではOwnerモデルの認証を突破することはできなくなる。
コードは以下になる。

<?php
public function beforeFilter(){
	if(isset($this->request->params['admin'])){
    	    $this->Auth->authenticate = array(
        	'Form' => array(
             	    'userModel' => 'Owner',
                    'fields' => array('username' => 'name','password'=>'password')
                )
            );
           $this->Auth->loginAction = array('controller' => 'users','action' => 'login', 'admin'=>true);
           $this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index', 'admin'=>true);
           $this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login', 'admin'=>true);
           AuthComponent::$sessionKey = "Auth.Owner";
        }else{
           $this->layout = 'user'; //レイアウトを切り替える。
           AuthComponent::$sessionKey = "Auth.User";
	}
}


次にUserControllerにAdminRouting用のメソッドを追加する。

<?php
public function admin_index(){
}
public function admin_login(){
	$this->login(); //すでに作成したUser用のlogin()を使いまわす。
}
public function admin_logout(){
	$this->logout(); //すでに作成したUser用のlogout()を使いまわす。
}


各ControllerでbeforeFilter()を実装する時は、
AppControllerのbeforeFilter()を実行するために
parent::beforeFilter() を実装しておくこと。


これで終わり。
キャッシュが効いてて運営者ログインと利用者ログインの切り替えが上手くいかない場合があるが、
利用者が運営者アカウントでログインすることはないから問題ないと思う。

結局AdminRoutingを判定して、
Authコンポーネントを動的に切り替えているだけなので、
ログインのURLとかはいくらでも調整可能です。