Yiiでユーザ認証まわりを作成する

Yiiでのユーザ認証まわりを修正します。

したいことは

  • パスワードについてはphpassを利用して暗号化したい。
  • ログインにはメルアドとパスワードを利用したい。
  • アクセスコントロールは管理者、マネージャー、一般ユーザのように簡単にしたい。

となります。

パスワードについてはphpassを利用して暗号化したい。

まず、ここから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 function 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 function getId()
  {
    return $this->_id;
  }
}

これで、認証の際に、入力されたパスワードをphpassで暗号化し、DBに保存されているパスワードと一致しているかチェックするようになります。

最後にUserテーブルへのデータ登録時に、パスワードをphpassで暗号化する部分を用意します。修正するファイルはprotected/models/User.phpです。

class User extends CActiveRecord
{
  protected function 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 function __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 function 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 function 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 function __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 function 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 function 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 function 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 function 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 function 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)
    ),
));
?>
投稿日:
カテゴリー: php タグ:

1件のコメント

コメントは受け付けていません。