ZendFramework2(ZF2)でログイン処理を行う認証モデルの作成からセッションでの状態保持



会員サイトを作成する際に必須となるログイン機能をモデルの作成からソースコード付きで解説します。

今回はZendFramework2でのログイン処理をコード付きで解説したいと思います。
認証用のロジックは汎用的に使いまわせる様にモデルとして作成し、
コントローラから認証が必要な際にモデルの機能を呼ぶといった実装内容とします。

モデルの機能と致しましては、
  • ログイン処理⇒認証を行い、認証結果をセッションとして保存する
  • ログアウト処理⇒セッションに保存された認証情報を破棄する
  • ログイン状態の確認⇒セッションの値を確認し、現在ログイン中が否かを取得
  • ログインユーザ情報の取得⇒セッションに保存されているユーザ情報を取得する

といった、ログイン関係に良く使うであろうシンプルなものとなっています。

後は状況によって追加して行ければモデルとしての機能も強化されるかと思います。

  1. 認証用モデルを作成しよう
  2. ログイン機能の実装
  3. ログアウトをサクッと実装しよう
  4. 現在のログイン状態を確認しよう
  5. ログインしているユーザの情報を取得しよう
  6. 作成した認証用モデルを使ってみよう




認証用モデルを作成しよう







まずは認証用モデルの枠組みを作成します。
今回は『Auth』というクラスを作成します。

認証用モデル作成に当たって、必要となるクラスを下記の様に読み込みます


namespace User\Model;

// AUTHアダプタクラス
use Zend\Authentication\Adapter\DbTable as AuthAdapter;

// AUTHサービス
use Zend\Authentication\AuthenticationService;

// AUTHセッションストレージ
use Zend\Authentication\Storage\Session as SessionStorage;

// 認証クラス
class Auth
{
 // ......
}






続いて、認証クラスの初期設定です。
認証クラスのメンバとして、サービスロケータと、認証用オブジェクトを保持します。
コンストラクタではサービスロケータを受け取りメンバへ格納。
認証用オブジェクトはセッションのネームスペースを指定して認証オブジェクトを作成し、
メンバに保持します。



 // サービスロケータ
 protected $service_locator;

 // 認証オブジェクト
 protected $auth;

 /* コンストラクタ
 ----------------------------------------------------------------------------------*/
 public function __construct( $service_locator )
 {
  // MVC巡廻のアプリ情報を取得する(コントローラ)
  $this->service_locator = $service_locator;

  // 認証情報を取得する
  $this->auth = new AuthenticationService( new SessionStorage( 'session_name_space' ) );
 }

認証用オブジェクトには永続的なログイン情報を保持する為に
作成時にセッション用のオブジェクトをセットしてあげます。
この時に設定するネームスペースが後のキーとなります。

サービスロケータを保持するのは、アプリのオートローダーで読み込んだ
DB設定等を参照する為です。



ココまで準備が出来ましたら、いよいよ機能の実装です。






ログイン機能の実装






ログイン機能に必要なのが、名前やメールアドレス等のログインユーザを識別する値と、
その値と遂になるパスワードが必要です。
今回作成するログイン機能では、引数としてキーとパスワードを受け取ります。



 /* ログイン
 ----------------------------------------------------------------------------------*/
 public function login( $key, $pass )
 {
  // DBアダプタの取得
  $dbAdapter = $this->service_locator->get('Zend\Db\Adapter\Adapter');

  // 認証アダプターを作成
  $authAdapter = new AuthAdapter( $dbAdapter );

  // 認証項目内容を設定
  $authAdapter
   // 検索するテーブル名
   ->setTableName('member')

   // 認証対象カラム
   ->setIdentityColumn( 'id_culumn_name' )

   // 認証パスワードカラム
   ->setCredentialColumn( 'password_culumn_name' );

  // デリートフラグが落ちている事も認証条件に加える
  $select = $authAdapter->getDbSelect();
  $select->where("deleted = 0");

  // フォームからの入力値をセットする
  $authAdapter
   // 認証対象値
   ->setIdentity( $key )

   // 認証パスワード
   ->setCredential( $pass );

  // 認証クエリを実行し、認証結果を保存する
  $result = $this->auth->authenticate( $authAdapter );

  // 認証成功(isValid()で認証結果のチェック)
  if( $result->isValid() ){

   // 認証ストレージを取得する
   $storage = $this->auth->getStorage();

   // 結果オブジェクトをストレージに書き込む
   $storage->write( $authAdapter->getResultRowObject() );

   return( true );
  }
  // 認証失敗
  else{
   return( false );
  }

 }

処理の流れと致しましては、まず認証を行うDBへのアクセス用アダプタを作成します。
先ほど保持したサービスロケータを仕様すれば、アプリ全体の設定からDB情報を使って作成出来るので便利です。

そのDBへのアダプタを認証アダプタとして登録します。

そのアダプタに、認証方法を設定していきます。

今回は、認証情報を検索するテーブル名と、
キーとなるカラム名、パスワードが格納されているカラム名を
それぞれ専用のセッタメソッドにて設定しています。


また、完全削除だけでなく、論理削除の様な形を取ったり、
他にも認証の条件を追加したい時はアダプタからセレクト条件を抽出し、
whereで条件を追加すれば認証時にも設定した条件が適用される様になります。


準備が整ったら入力されたパラメータ(今回は引数で受け取った)値をセットしましょう。

それぞれに、キーとパスワードと遂になる様にセットし、
authenticate()で認証結果を取得します。
認証結果の有効性はisValid()で判定します。

認証に成功したら、認証オブジェクト作成時に追加したセッションストレージを取得し、
認証結果として取得出来たユーザ情報をセッションに書き込んで
ログイン処理の完了です。







ログアウトをサクッと実装しよう





ログアウト処理は簡単で、保存した認証情報とセッション内の値をクリアする事で
ログアウトは実装出来ます。


 /* ログアウト
 ----------------------------------------------------------------------------------*/
 public function logout()
 {
  // ストレージと認証情報を破棄する
  $this->auth->getStorage()->clear();
  $this->auth->clearIdentity();
 }

コンストラクタでセッションオブジェクトを新規で毎回作成しますが、
セッション名をセットしているので、該当するセッションは自動的に読み込まれます。

ストレージの値を破棄するにはclear()メソッドを使用します。

認証状況を破棄するにはclearIdentity()メソッドを使用します。

これで認証情報は破棄されますので、ログアウトが完了します。





現在のログイン状態を確認しよう




今現在ログイン状態にあるのか否かを判定する為のメソッドを定義します。


 /* ログイン状態を確認(ログイン状態ならログイン情報を返却)
 ----------------------------------------------------------------------------------*/
 public function checkLogin()
 {
  // ログイン確認
  if( $this->auth->hasIdentity() ){
   return( true );
  }
  return( false );
 }



認証状況はhasIdentity()で確認する事が出来ます。
今回作成したメソッドは、単純にhasIdentity()によって認証が確認出来ればtrueを返し
認証が認められなければfalseを返却します。





ログインしているユーザの情報を取得しよう





ログインしているユーザの名前や登録日等、ログイン中のユーザの情報を知りたい機会は頻繁に訪れます。
そこで、ログインユーザ情報を取得するメソッドも定義しておきましょう。



 /* ログインユーザの情報を取得
 ----------------------------------------------------------------------------------*/
 public function getLoginUser()
 {
  // ログイン確認
  if( $this->auth->hasIdentity() ){
   // ログイン情報を取得する
   $identity = $this->auth->getIdentity();

   return( $identity );
  }
  return( false );
 }

認証されている場合、getIdentity()にて認証されているユーザ情報を取得し、
返却しています。勿論、ログインされていなければユーザ情報は取得出来ませんので、
ログインチェックを行った後に未ログインの場合はfalseを返却します。








ここまでで簡単なモデルの作成は以上です。次は作成した認証用モデルの使用方法です。








作成した認証用モデルを使ってみよう





まずは作成したモデルを読み込みます


// 認証モデルを読み込む
use User\Model\Auth as Auth;





ログイン処理では、パラメータを受け取りモデルに渡して
認証結果によって別画面へリダイレクトします。



 /* ログイン認証処理
 ----------------------------------------------------------------------------------*/
    public function authAction()
    {

  // パラメータを取得する
  $login_name = $this->params()->fromPost( 'key', "" );
  $login_pass = $this->params()->fromPost( 'pass', "" );

  $auth = new Auth( $this->getServiceLocator() );
  if( $auth->login( $key, $lpass ) ){
   // 会員画面へリダイレクト
   return( $this->redirect()->toUrl( 'member_area' ) );
  }
  else{
   // ログイン画面へリダイレクト
   return( $this->redirect()->toUrl( 'login' ) );
  }
    }




ログアウトはログアウトメソッドを呼んでログイン画面へリダイレクトさせましょう。


 /* ログアウト
 ----------------------------------------------------------------------------------*/
    public function logoutAction()
    {

  // ログアウト処理
  $auth = new Auth( $this->getServiceLocator() );
  $auth->logout();

  // ログイン画面へリダイレクト
  return( $this->redirect()->toUrl( 'login' ) );
    }




必ずログインが必要なアクション用にフィルタを掛けるためには、
認証状況を確認して、未ログイン時にはログイン画面へ強制リダイレクトさせるメソッドも用意すると便利です。


 /* ログイン権限が必要(権限無しはリダイレクト
 ----------------------------------------------------------------------------------*/
 public function mustLogin()
 {
  // 認証を確認する
  $auth = new Auth( $this->getServiceLocator() );
  if( ! $auth->checkLogin() ){
   return( $this->redirect()->toUrl( 'login' ) );
  }
 }


mustLogin()をアクションの先頭で呼ぶ事で、
アクション毎に権限の必須が設けられます。







また、ログインユーザのロールを確認する場合はモデルで作成したgetLoginUser()から
ユーザ情報を取得し、ユーザ情報の内容によって切り分ける事で対応可能です。

その場合、getLoginUserRole()等として、モデル内に作成するのが良いでしょう。


後は作成するサイトの内容による所が大きいと思いますので、
便利な機能を盛り込んで行って下さいね。




会員専用エリアを設ける様なサイトには必須となる認証処理ですので、
是非とも参考にして頂ければ幸いです。


11 Responses to ZendFramework2(ZF2)でログイン処理を行う認証モデルの作成からセッションでの状態保持

  1. はじめまして。
    匿名にて失礼いたします。

    $session = new Container('user');
    としてセッションを用意し、
    値を書き込む処理(例えば以下のような)を繰り返すと、
    $session->username = 'Johnny';
    次のようなエラーが表示されてしまいます。

    File:
    /var/www/html/vendor/ZF2/library/Zend/Session/AbstractContainer.php:202
    Message:
    Container cannot write to storage due to type mismatch

    原因や対策など何かご教授いただけないでしょうか。
    何卒よろしくお願い申し上げます。

    返信削除
    返信
    1. お世話になります。
      この度はコメント頂き、誠に有難うございます。

      エラーの内容を拝見致しますと、どうやら古いセッションが悪さする時に
      発生するエラーの様です。
      一度セッションをクリアしてから再度お試し頂けますでしょうか?

      それでも解決しない場合は、セッションマネージャの設定か、
      サーバー周りの設定を確認する必要があるかと思います。

      他にも、何かご不明な点が御座いましたら
      お気軽にコメント下さい。
      今後ともよろしくお願いいたします。

      削除
    2. ご返信いただきありがとうございます。
      サーバー内のセッションファイルを削除し、再度試しましたが同じ結果となってしまいました。
      セッションマネージャーやサーバーの設定を見直したいと思いますが、
      それぞれ具体的に確認すべきポイントなどはありますでしょうか?
      よろしくお願いいたします。

      削除
    3. お世話になります。
      この度はご返信頂き、誠に有難う御座います。

      ちなみに、ご使用のバージョンはいくつでしょうか?

      2.1.1以前をお使いでしたら、その辺りにバグがあるようです。
      https://github.com/zendframework/zf2/issues/3700

      また、サーバー周りの確認事項としては、
      php.iniにてSession.autostartの値がオンになっていないか?等の基本的な
      設定を確認してみて下さい。

      セッションマネージャーに関しましては、そこまで数が多くは無いので
      オプションの設定を全て見直して下さい。

      他にも、何かご不明な点が御座いましたら
      お気軽にコメント下さい。
      今後ともよろしくお願いいたします。

      削除
    4. お世話になります。
      サーバーの設定変更およびzend2のアップデートを行ったところ、
      無事セッションが使用できるようになりました。
      的確なご回答誠にありがとうございました。
      今後も当サイトを参考にさせていただきます。
      何卒よろしくお願いいたします。

      削除
    5. お世話になります。
      この度はご返信頂き、誠に有難う御座います。

      無事に動作した様で何よりです。

      他にも、何かご不明な点が御座いましたら
      お気軽にコメント下さい。
      今後ともよろしくお願いいたします。

      削除
  2. お世話になります。

    こちらの記事を参考にZend2でログイン機能を作成したのですが、
    Chromeのみ、ログイン後の画面から次のアクション時にログインページに遷移してしまいます。

    $this->auth->hasIdentity()をerror_logで出力してみたところ、
    ログイン後には1が出力されますが、違うページに遷移すると何も出力されません。

    また、mustLogin()の先頭に echo "<span></span>" などと何かしらの文字を出力すると何故かログアウトされません。

    この状況について、なにかご存知でしたらご教示いただけないでしょうか。

    環境は以下のようになります。
    Zend2 => ZendFramework-2.1.5
    PHP => PHP Version 5.3.15

    よろしくお願いいたします。

    返信削除
    返信
    1. お世話になります。
      この度はコメント頂きまして、誠に有難うございます。

      お話をお聞きする限りでは、
      Chromeでクッキーの保存が出来ていない可能性があります。

      基本的にPHPはサーバーサイドの処理ですので、
      ブラウザによって動作が異なるという事はありません。
      あるとすれば、セッションを読み込む際に、
      ブラウザからクッキーを読み取る事が出来ない場合、
      ログイン中という情報が無いのでログイン画面へ遷移してしまうと考えられます。

      Chromeにて、クッキーの取り扱いの設定をご確認下さい。

      しかし、何かしらの文字列を出力するとログアウトされないという原因は
      また別かと思います。

      きっと、ログイン確認を行ってログイン画面へ遷移させる処理の前に、
      何か別の問題がある様に思えます。

      その辺りももう一度ご確認頂ければ幸いです。

      他にも何かご不明な点が御座いましたら
      お気軽にコメント下さい。
      今後ともよろしくお願いいたします。

      削除
    2. お世話になります。

      ご返信いただきまして、大変有難うございます。

      Cookie関連のアドバイスをいただき、色々と確認したりphp.iniの設定を確認してみたのですが、
      依然、ログアウトしてしまいます。

      何点か分かった事がありまして、
      module/admin で全く同じログイン機能を実装すると問題なくログイン状態が維持されますが、
      module/ship というモジュールをでは今回の件が起こってしまいます。

      zend2とcookie関連のバグ(?)があるのかと思い検索していましたら、
      http://forums.zend.com/viewtopic.php?f=69&t=114043
      のような記事を見つけました。

      何度も質問してしまい、大変申し訳ありませんが、
      上記記事も含めまして、なにか思い当たるところはございますでしょうか?

      よろしくお願いいたします。

      削除
  3. お世話になります。

    その後、色々と調査してみたところ、なんとか問題解決にいたりました。
    ご丁寧に対応いただき、大変有難うございました。

    よろしくお願いいたします。

    返信削除
    返信
    1. お世話になります。
      この度はご連絡頂きまして誠に有難う御座います。
      無事に解決された様で何よりです。

      >zend2とcookie関連のバグ(?)があるのかと思い検索していましたら、
      >http://forums.zend.com/viewtopic.php?f=69&t=114043
      >のような記事を見つけました。

      ちなみに、解決方法は上記のURL内で記載されている
      クッキーの有効期限を再設定する方法を行ったのでしょうか?

      もし宜しければ、実際に解決に至った経緯を教えて頂ければ幸いです。
      何卒、宜しくお願い致します。

      削除

人気の投稿

Category

Algorithm (2) Android (8) ASP/aspx (1) Blogger (2) C/C++ (1) Chrome (5) CSS (9) Firefox (4) Fortran (1) Google (9) GoogleMap (2) HTML (12) IE (3) Information (4) iOS (2) iPhone/iPad/iPod (2) Java (6) JavaScript (16) jQuery (9) JSP (1) LifeRecipe (5) Linux (2) Macintosh (2) MapKit (4) Marketing (7) MySQL (3) NAMAZU (2) Objective-C (7) Other (7) Perl (1) PHP (9) Python (1) RSS/Atom (2) Ruby (1) Safari (2) SEO (11) Smarty (2) SQL (2) Tex (1) Three.js (1) Twitter (1) TwitterLog (313) UIKit (5) Unix (1) VBA/VBS (1) Windows (5) WordPress (3) Writing (5) XAMPP (1) XML (1) Yahoo (2) ZendFramework2 (14)

Archives