Yiiでのユーザ認証まわりを修正します。
したいことは
となります。
まず、ここからphpassの最新版をダウンロードし、protected/extensions/passwordHash/PasswordHash.phpとして保存します。
次にmain.phpにphpassの設定を追記します。
'import'=>array(
// for phpass
'application.extensions.passwordHash.PasswordHash',
),
'params'=>array(
//for phpass
'phpass'=>array(
'iteration_count_log2' => 8,
'portable_hashes' => false,
),
), なお、iteration_count_log2 はストレッチングに利用する数値です。例えば8の場合、2^8=256回、暗号化アルゴリズムが適用されます。この数値は4から31の間で設定します。
portable_hashesをtrueに設定すると、暗号化したパスワードにsaltを含むことになり、あまり推奨されません。phpのバージョンが5.3以上ならfalseにしたほうがよいです。
認証の処理を修正する必要があるので、protected/components/以下にUserIdentity.phpを作成し、以下のような内容を記述します。
/**
* UserIdentity represents the data needed to identity a user.
* It contains the authentication method that checks if the provided
* data can identity the user.
*/class UserIdentity extends CUserIdentity
{
private $_id;
/**
* Authenticates a user.
* The example implementation makes sure if the username and password
* are both 'demo'.
* In practical applications, this should be changed to authenticate
* against some persistent user identity storage (e.g. database).
* @return boolean whether authentication succeeds.
*/ public authenticate()
{
$user=User::model()->findByAttributes(array('username'=>$this->username));
$ph = new PasswordHash(
Yii::app()->params['phpass']['iteration_count_log2'],
Yii::app()->params['phpass']['portable_hashes']
);
if($user==null)
$this->errorCode=self::ERROR_USERNAME_INVALID;
else if(!$ph->CheckPassword($this->password, $user->password))
$this->errorCode=self::ERROR_PASSWORD_INVALID;
else
{
$this->_id=$user->id;
$this->errorCode=self::ERROR_NONE;
}
return $this->errorCode==self::ERROR_NONE;
}
public getId()
{
return $this->_id;
}
} これで、認証の際に、入力されたパスワードをphpassで暗号化し、DBに保存されているパスワードと一致しているかチェックするようになります。
最後にUserテーブルへのデータ登録時に、パスワードをphpassで暗号化する部分を用意します。修正するファイルはprotected/models/User.phpです。
class User extends CActiveRecord
{
protected beforeSave()
{
if(!empty($this->password))
{
$ph = new PasswordHash(
Yii::app()->params['phpass']['iteration_count_log2'],
Yii::app()->params['phpass']['portable_hashes']
);
$this->password = $ph->HashPassword($this->password);
}
return parent::beforeSave();
}
} 作業としては、Userテーブルにemailフィールドを追加し、Userモデルにも必要な設定を行った後、ログインページと認証部分の修正となります。
ログインページの修正については、protected/views/site/login.phpを以下のような感じに修正します。なお、Yii-bootstarpを利用しているので、widgetはbootstarpのものを利用しています。Yii-bootstrapの詳しい書式については、こちらを参考にしてください。
<?php
$this->pageTitle=Yii::app()->name . ' - ログイン';
$this->breadcrumbs=array(
'ログイン',
);
?>
<h1>ログイン</h1>
登録されたメールアドレスとパスワードを利用してログインしてください。<br />
<div class="form">
<?php $form=$this->beginWidget('bootstrap.widgets.TbActiveForm', array(
'id'=>'login-form',
'focus'=>array($model,'email'),
)); ?>
<p class="help-block">「 <span class="required">*</span> 」のある欄は入力必須です。</p>
<?php echo $form->errorSummary($model); ?>
<?php echo $form->textFieldRow($model,'email',array('class'=>'span5','maxlength'=>128)); ?>
<?php echo $form->passwordFieldRow($model,'password',array('class'=>'span5','maxlength'=>128)); ?>
<?php echo $form->checkBoxRow($model,'rememberMe'); ?>
<div class="form-actions">
<?php $this->widget('bootstrap.widgets.TbButton', array(
'buttonType'=>'submit',
'type'=>'primary',
'label'=>'ログイン',
)); ?>
</div>
<?php $this->endWidget(); ?>
</div><!-- form --> 認証部分は、先に出てきたprotected/components/UserIdentity.phpを修正します。
/**
* UserIdentity represents the data needed to identity a user.
* It contains the authentication method that checks if the provided
* data can identity the user.
*/class UserIdentity extends CUserIdentity
{
const ERROR_EMAIL_INVALID=3;
private $_id;
public $email;
public __construct($email,$password)
{
$this->email=$email;
$this->password=$password;
}
/**
* Authenticates a user.
* The example implementation makes sure if the username and password
* are both 'demo'.
* In practical applications, this should be changed to authenticate
* against some persistent user identity storage (e.g. database).
* @return boolean whether authentication succeeds.
*/ public authenticate()
{
$email=strtolower($this->email);
$user=User::model()->find('email=?',array($email));
$ph = new PasswordHash(
Yii::app()->params['phpass']['iteration_count_log2'],
Yii::app()->params['phpass']['portable_hashes']
);
if($user==null)
$this->errorCode=self::ERROR_EMAIL_INVALID;
else if(!$ph->CheckPassword($this->password, $user->password))
$this->errorCode=self::ERROR_PASSWORD_INVALID;
else
{
$this->_id=$user->id;
$this->errorCode=self::ERROR_NONE;
}
return $this->errorCode==self::ERROR_NONE;
}
public getId()
{
return $this->_id;
}
} 作業としては、Userテーブルにroleフィールドを用意したあと、Userモデルや認証部分、表示部分などの修正となります。
ロールについては、管理者(3)、マネージャー(2)、一般ユーザ(1)とします。
Userモデルでは、以下のような修正をします。
class User extends CActiveRecord
{
const USER_ROLE_ADMIN=3;
const USER_ROLE_MANAGER=2;
const USER_ROLE_USER=1;
} 認証部分は、先に出てきたprotected/components/UserIdentity.phpを修正します。
/**
* UserIdentity represents the data needed to identity a user.
* It contains the authentication method that checks if the provided
* data can identity the user.
*/class UserIdentity extends CUserIdentity
{
const ERROR_EMAIL_INVALID=3;
private $_id;
public $email;
public __construct($email,$password)
{
$this->email=$email;
$this->password=$password;
}
/**
* Authenticates a user.
* The example implementation makes sure if the username and password
* are both 'demo'.
* In practical applications, this should be changed to authenticate
* against some persistent user identity storage (e.g. database).
* @return boolean whether authentication succeeds.
*/ public authenticate()
{
$email=strtolower($this->email);
$user=User::model()->find('email=?',array($email));
$ph = new PasswordHash(
Yii::app()->params['phpass']['iteration_count_log2'],
Yii::app()->params['phpass']['portable_hashes']
);
if($user==null)
$this->errorCode=self::ERROR_EMAIL_INVALID;
else if(!$ph->CheckPassword($this->password, $user->password))
$this->errorCode=self::ERROR_PASSWORD_INVALID;
else
{
$this->_id=$user->id;
$this->setState('role', $user->role);
$this->errorCode=self::ERROR_NONE;
}
return $this->errorCode==self::ERROR_NONE;
}
public getId()
{
return $this->_id;
}
} 追加したのは
$this->setState('role', $user->role); の部分です。この処理でセッションにroleの内容を書き出し、必要な箇所で利用できるようにします。なお、セッションからの取り出しには、
Yii::app()->user->getState('roles'); か
Yii::app()->user->roles
を利用します。
アクセスコントロール部分をprotected/components/以下にWebUser.phpとして新規に作成します。
class WebUser extends CWebUser
{
/**
* Overrides a Yii method that is used for roles in controllers (accessRules).
*
* @param string $operation Name of the operation required (here, a role).
* @param integer $user_id (opt)
* @return bool Permission granted?
*/ public checkAccess($operation, $user_id=null)
{
if (empty($this->id)) {
// Not identified => no rights
return false;
}
$role = $this->getState("role");
if ($role == User::USER_ROLE_ADMIN) {
return true; // admin role has access to everything
}
if (strstr($operation,$role) !== false) { // Check if multiple roles are available
if(empty($user_id))
return true;
else
return ($this->id==$user_id);
}
// allow access if the operation request is the current user's role
return ($operation == $role);
}
} また、この処理を利用できるようにprotected/config/main.phpに追記しておきます。
'components'=>array(
'user'=>array(
'class' => 'WebUser',
),
), コントローラのアクションでアクセス制限をかける場合は、以下のようにします。
/**
* @return array action filters
*/ public filters()
{
return array(
'accessControl', // perform access control for CRUD operations
);
}
/**
* Specifies the access control rules.
* This method is used by the 'accessControl' filter.
* @return array access control rules
*/ public accessRules()
{
return array(
array('allow', // allow all users
'actions'=>array('view'),
'users'=>array('*'),
),
array('allow', // allow authenticated user
'actions'=>array('create','update','delete'),
'users'=>array('@'),
),
array('allow', // allow admin user
'actions'=>array('admin'),
'roles'=>array(User::USER_ROLE_ADMIN),
),
array('deny', // deny all users
'users'=>array('*'),
),
);
} 上記の場合、adminアクションはroleがUser::USER_ROLE_ADMINの人、つまり管理者しかアクセスできないとなります。
ビューで表示をコントロールする場合は、以下のような感じに書きます。
<?php if(Yii::app()->user->checkAccess(User::USER_ROLE_ADMIN, User::USER_ROLE_MANAGER)) :?> 管理者とマネージャー向けの表示 <?php else: ?> 管理者とマネージャー以外の方向けの表示 <?php endif; ?>
CMenu Widgetなどで利用する場合は、以下のような感じに書きます。
<?php
$this->widget('zii.widgets.CMenu',array(
'items'=>array(
array('label'=>'Admin', 'url'=>array('/manageUser/admin'), 'visible'=>Yii::app()->user->checkAccess(User::USER_ROLE_ADMIN)),
array('label'=>'Login', 'url'=>array('/site/login'), 'visible'=>Yii::app()->user->isGuest),
array('label'=>'Logout ('.Yii::app()->user->name.')', 'url'=>array('/site/logout'), 'visible'=>!Yii::app()->user->isGuest)
),
));
?>