WinRoadのLaravel4初心者講座

次世代PHPフレームワークのLaravel4を初心者向けに解説して参ります。

Winroad徒然草の管理人がお届けします
Sentry2

ログインフィルターの作成及び適用

前回は、Sentry2でログインする方法を調べましたので、今回は、Sentry2 用のログインフィルターの作成及び適用について調べてみたいと思います。

デフォルトのフィルター

その前にまず、Laravel4のデフォルトのフィルターを見てみましょう。

laravel/app/filters.php

<?php
App::before(function($request){
//
 });
 App::after(function($request, $response){
 //
 });
Route::filter('auth', function(){
if (Auth::guest()) return Redirect::guest('login');
 });
Route::filter('auth.basic', function(){
 return Auth::basic();
 });
Route::filter('guest', function(){
if (Auth::check()) return Redirect::to('/');
 });
Route::filter('csrf', function(){
if (Session::token() != Input::get('_token')){
 throw new Illuminate\Session\TokenMismatchException;
 }
 });
  • 2-7行目:アプリケーションの開始前と終了後に動作させるイベントを定義することが出来ます。ここには、プロジェクト全体で適用するフィルターを作成します。
  • 8-10行目:Laravel標準添付の認証フィルターです。Auth::attempt()で認証した場合、適用されます。今回はこれは使用しません。
  • 11-13行目:ベーシック認証を使用するためのフィルターです。
  • 14-16行目:guestフィルターです。authフィルターと逆の働きをしています。authフィルターがログインしていなければ、loginページへリダイレクトしているのに対し、guestフィルターは、ログインしていれば、トップページへ移動するように記述しています。
  • 17-20行目:CSRF対策のためのフィルターです。セッションのトークンとフォームから送信されてきたトークンが異なった場合にトークンエラー(TokenMismaatchExcrption)がスローされる(投げかけられる)ように作られていますので、例外処理用のロジックを作成した方が無難だと思います。

Sentry用のフィルター

それでは、filters.phpにSentry2用のフィルターを追加します。

//Sentryフィルター
Route::filter('sentry', function(){
 if ( ! Sentry::check()) return Redirect::to('login');
});
    • 2行目:’sentry’名(任意)でフィルターを作成します。
    • 3行目:Sentry::checkメソッドは、Sentry2を使用して認証している事を確認するためのメソッドですので、ここでは、もし認証されていなければ、Loginコントローラのindex.phpへ移動するように指定しています。

ルーターでフィルターの適用

フィルターを適用するには、ルーター(routes.php)にフィルターを記述する方法と、コントローラにフィルターを記述する方法があります。

まず、先にルーターにフィルターを適用する方法を調べてみます。Route::getメソッドの、第2引数の、’before’にsentryフィルターを指定します。

laravel/app/routes.php

Route::get('/', array('before'=>'sentry',function()
{
 return View::make('hello');
}));

コントローラにフィルターの適用

次に、コントローラ内にフィルターを適用する方法を調べてみます。コントローラ内にフィルターを適用するには、コンストラクターに記述します。

//コンストラクター
 public function __construct(){
 $this->beforeFilter('sentry');
 $this->beforeFilter('csrf',array('on'=>'post'));
 }
  •  3行目:beroreFilterメソッドの第1引数で適用するフィルターを指定します。第2引数は、オプションで’only’や’except’プロパティを指定することが出来ます。

    //例:getIndexのみにフィルターを適用する
    $this->beforeFilter(‘sentry’,’only’=>getIndex());

  • 4行目:CSRFフィルターの適用例です。POST送信時のみに適用されます。

try~catch構文でログイン処理

Sentry2のドキュメントにあるように、try~catch構文でログイン処理する方法を調べてみたいと思います。

  1. try~catch構文は、PHP5.0以降に採用された構文で例外処理を行います。tryで例外の発生する可能性のあるロジックを記述し、例外を捕捉したら、発生した例外に応じてcatch以降のロジックを実行します。
  2. 下記にpostIndex()を記述します。
    laravel/app/controllers/LoginController.php

    public function postIndex(){
     try{
     //フォームからemailとpasswordの連想配列を取得
     $login=Input::only('email','password');
     //ログインの実行
     $user=Sentry::authenticate($login,Input::get('remember'));
     //ログイン後トップページへリダイレクト
     return Redirect::to('/');
     }
     //例外を捕捉したら
     catch (Cartalyst\Sentry\Users\LoginRequiredException $e){
     //元のページへリダイレクト
     return Redirect::back()
     ->withErrors(array('warning'=>'E-mailとパスワードを入力して下さい'))
     ->withInput();
     }
     catch (Cartalyst\Sentry\Users\PasswordRequiredException $e){
     return Redirect::back()
     ->withErrors(array('warning'=>'パスワードを入力して下さい'))
     ->withInput();
     }
     catch (Cartalyst\Sentry\Users\UserNotActivatedException $e){
     return Redirect::back()
     ->withErrors(array('warning'=>'アクティベートされていません'))
     ->withInput();
     }
     catch (Cartalyst\Sentry\Users\UserNotFoundException $e){
     return Redirect::back()
     ->withErrors(array('warning'=>'該当者がいません'))
     ->withInput();
     }
     }
    • 6行目:Sentry::authenticate()メソッドで認証を行います。第1引数に、認証するための情報(emailとpassoword)を指定します。第2引数には、ログイン状態を保持するかどうかをtrue又は、falseで指定します。
    • 6行目:Input::get()メソッドで受け取った値を第2引数に指定しています。checkboxにチェックを入れていれば下記のようにブラウザにcartalyst_sentry名のクッキー(有効期限:5年間)を保存します。
      laravel012
    • 8行目:ログイン後にトップページへ移動します。
    • 11行目:必須ログイン情報が無かったら、元のページ(ログインページ)へ移動します。
    • 14-15行目:エラー情報と入力値を保持したままリダイレクトします。
    • 17行目:パスワードが入力されていなかったら、元のページへリダイレクトします。
    • 22行目:emailとパスワードが一致していてもアクティベートされていなければ、元のページへリダイレクトします。アクティベートされていない場合は、元のページでは無く、別のページを用意しておいた方がいいかもしれませんね。
    • 27行目:ユーザーが見つからなかった場合に、元のページへリダイレクトします。
  3. ビューファイルを下記のように修正します。
    laravel/app/views/login/index.blade.php

    <!doctype html>
     <html>
     <head>
     <meta charset="utf-8">
     <title>Login</title>
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     {{HTML::style('tbs/css/bootstrap.min.css')}}
     {{HTML::style('tbs/css/signin.css')}}</head>
    <body>
    <div class="container">
     {{Form::open(array('url'=>'login/index','class'=>'form-signin'))}}
     <h2 class="form-signin-heading">会員専用サイト</h2>
     {{Form::text('email','',array('class'=>'form-control','placeholder'=>'E-mail'))}}
     {{Form::password('password',array('class'=>'form-control','placeholder'=>'Password'))}}
     @if($errors->has('warning'))
     <div class="alert alert-danger">
     {{$errors->first('warning')}}
     </div>
     @endif
     {{Form::checkbox('remember',1,true)}} ログイン状態を保持する<br><br>
     {{Form::submit('ログイン',array('class'=>'btn btn-lg btn-primary btn-block'))}}
     {{Form::close()}}
    </div> <!-- /container -->
    </body>
     </html>
    • 修正(追加)箇所は、15-19行目です。
    • エラーがあった場合、下記のように入力フィールドの下にエラー文字を表示します。
    • 尚、Sentry2では、コントローラでwithInput()を指定して、リダイレクトすると、ビューファイルは何も変更せずに、元の入力値を表示します。つまり、Form::text()メソッドの第2引数に、Input::old(‘email’)を記述しなくても元の入力値を表示します。
      laravel013
  4. emailとpasswordが一致した場合、トップページへ移動します。
  5. 尚、このままでは、只単にユーザー認証して、トップページへ移動しただけです。認証機能を使うためには、認証していない人は、指定のページへアクセスできないようにしなければなりません。
  6. 次回は、認証していない人は指定のページへアクセスできないようにフィルターをかける方法を調べてみたいと思います。

ログインフォームから値を受け取る

1. 前回作成したログインフォームからどのような値が送信されているのかを調べるために、postIndex()を下記のように作成します。

laravel/app/controllers/LoginController.php

public function postIndex(){
 dd(Input::all());
 }
  • dd()は、指定された変数の内容を表示し、スクリプトの実行を停止するヘルパー関数です。PHPのvar_dump()関数と同じような働きをします。※正確には、return var_dump();と同じ働きをします。
  • Input::allメソッドは、フォームから送信された全入力値の連想配列を取得します。

2. フォームから、値を入力して送信してみます。

laravel010

3. 下記のように表示されました。

laravel011

4. フォームから送信されたデータには”_token”、”email”、”password”、”remember”があります。このうちの”_token”は、前回作成したフォームの中には含まれていませんでした。

5. laravelのフォームはフォーム内に隠しフィールド(hidden)としてトークンを作成しなくても、自動的にフォームから送信されるような仕様に変更されています(※Form::openで開始した場合)

6. 『Formクラスの基礎』の中で、CSRF対策として明示的にForm::tokenメソッドで、トークンを隠しフィールドとして追加するように記述していますが、この処理は不要となります。

7. ですので、フォームを作成するときには、トークンについては一切考慮する必要はありません。

8. 只、受け取ったコントローラ内で処理する必要がありますが、実際問題としてこの処理もフィルターに任せることによりCSRF対策に関しては全く考慮する必要がなくなります。

フォームからの値の受け取り方

フォームから値を受け取るには、下記のようなInputクラスを使用します。

  • Input::get(‘フィールド名’)
    指定のフィールドの値のみを取得します。
  • Input::all()
    フォーム送信された全てのフィールド名と値を連想配列として取得します。
  • Input::only(‘フィールド1′,’フィールド2’,….)
    フォームから送信されたデータのうち、指定されたフィールド名と値の連想配列を取得します。
  • Input::excetp(フィールド1,フィールド2,….)
    フォームから送信された全てのデータの中から、引数に指定されたフィールド以外のデータのフィールド名と値の連想配列を取得します。
  • Input::has(‘フィールド名’)
    指定のフィールド名の入力値が存在するかどうかを確認するのに使用します。

ちょっと横道にそれましたが、次回こそは、Sentry2を使用してのログイン方法をご紹介します。

ログインフォームの作成

1. ログインコントローラを作成して、まず最初にログインフォームを表示するためのgetIndex()メソッドを作成します。

laravel/app/controllers/LoginController.php

<?php
 class LoginController extends BaseController{
 //ログインフォームの表示
 public function getIndex(){
 return View::make('login/index');
 }
 }

2. ログイン用のフォームを作成するのですが、その前にTwitter Bootstrap3.0を使う準備をしたいと思います。

3. 下記のサイトからTwitter Bootstrap3.0をダウンロードして適当な箇所に解凍して下さい。

http://getbootstrap.com/

4. ダウンロードしたTwitter Bootstrap3.0は、かなりのフォルダ数、ファイル数がありますので、Twitter Bootstrap2.0系統を使用していた人でも、戸惑いそうです。

5. distフォルダ内にあるフォルダ(css、js、font)フォルダをpublic/tbsフォルダにコピーします。※tbsフォルダは事前に作成しておきます。

6. そして、今回ログインフォーム用に使用するCSSファイルをexamples/singin/singin.cssから、public/tbs/cssフォルダ内に保存します。

laravel08

7.  signin.cssファイルと同じフォルダ内にあるindex.htmlをindex.blade.php名に変更して、中身を下記のように修正して、viewsフォルダにloginフォルダを作成し、その中に保存します。

laravel/app/views/login/index.blade.php

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Login</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{HTML::style('tbs/css/bootstrap.min.css')}}
{{HTML::style('tbs/css/signin.css')}}
</head>
<body>
<div class="container">
{{Form::open(array('url'=>'login/index','class'=>'form-signin'))}}
 <h2 class="form-signin-heading">会員専用サイト</h2>
{{Form::text('email','',array('class'=>'form-control','placeholder'=>'E-mailアドレス'))}}
{{Form::password('password',array('class'=>'form-control','placeholder'=>'パスワード'))}}
{{Form::checkbox('remember',1,true)}} ログイン状態を保持する<br><br>
{{Form::submit('ログイン',array('class'=>'btn btn-lg btn-primary btn-block'))}}
{{Form::close()}}
</div> <!-- /container -->
</body>
</html>
  • 7行目:HTML::styleメソッドは、CSSファイルをpublicフォルダより読み込みます。ここでは、publicフォルダ内にtbsフォルダ作成し、その中にTwitter Bootstrap3.0のcssフォルダを保存しています。
  • 12行目:Form::openメソッドで、フォームを開始します。’url’でログインコントローラのgetIndexメソッドを指定しています。
  • 14行目:Form::textメソッドで、テキストフィールドを作成します。第1引数にフィールド名、第2引数はデフォルト値、第3引数は、パラメーターを連想配列で指定します。
  • 15行目:Form::passwordメソッドで、パスワードフィールドを作成します。第1引数に、パスワードフィールド名、第2引数にパラメーターを連想配列で指定します。
  • 16行目:Form::checkboxメソッドで、チェックボックスを作成します。第1引数にチェックボックス名、第2引数に値を指定します。第3引数はオプションで、trueを指定すると、初期値がチェック状態になります。
  • 17行目:Form::submitメソッドで、Submitボタンを作成します。第1引数に表示名、第2引数にはパラメーターを連想配列で指定します。

8. ブラウザからアクセスしてみます。下記のようになりました。Twitter Bootstrapはデザインの苦手な人でも簡単にレイアウトが作成できますので、便利ですね。※会員専用サイトの文字の箇所には、サイトのロゴを作成して表示するようにした方が良さそうですね。

laravel009

9. 次回は、フォームから受け取った値を処理する方法をご紹介します。

基本グループ及び初期ユーザーの作成

Sentry2のログインコントローラを作成する前に、基本グループ(Administrators、Moderators、Users)及び初期ユーザーを作成したいと思います。

1. RESTフルなコントローラとして、SetupControllerを下記のように作成します。

laravel/app/controllers/SetupController.php

<?php
 class SetupController extends BaseController {
public function getIndex(){
 return View::make('setup/index');
 }
 public function getSentryInit(){
 try{
 $group = Sentry::getGroupProvider()->create(array(
 'name' => 'Administrators',
 'permissions' => array(
 'admin' => 1,
 'user' => 1,
 ),
 ));
 }
 catch (Cartalyst\Sentry\Groups\GroupExistsException $e)
 {
 echo 'Administratorグループは既に存在しています。<br>';
 }
try
 {
 // Moderatorグループの作成
 $group = Sentry::getGroupProvider()->create(array(
 'name' => 'Moderators',
 'permissions' => array(
 'admin.view' => 1,
 'user.create' => 1,
 'user.delete' => 0,
 'user.view' => 1,
 'user.update' => 1,
 ),
 ));
 }
 catch (Cartalyst\Sentry\Groups\GroupExistsException $e)
 {
 echo 'Moderatorsグループは既に存在しています。<br>';
 }
try
 {
 // Userグループの作成
 $group = Sentry::getGroupProvider()->create(array(
 'name' => 'Users',
 'permissions' => array(
 'admin' => 0,
 'user.create' => 1,
 'user.delete' => 0,
 'user.view' => 1,
 'user.update' => 1,
 ),
 ));
 }
 catch (Cartalyst\Sentry\Groups\GroupExistsException $e)
 {
 echo 'Usersグループは既に存在しています。<br>';
 }
try
 {
 // ユーザーの作成
 $user = Sentry::getUserProvider()->create(array(
 'email' => 'winroad@gmail.com',
 'password' => 'winroad',
 'activated' => 1,
 ));
 //グループIDを使用してグループを検索
 $adminGroup = Sentry::getGroupProvider()->findById(1);
 // ユーザーにadminグループを割り当てる
 $user->addGroup($adminGroup);
 }
 catch (Cartalyst\Sentry\Users\UserExistsException $e)
 {
 echo 'このログインユーザーは存在します。<br>';
 }
 }
 }
  •  6行目:getSentryInit()のようなキャメル記法にブラウザからアクセスするには、setup/sentry-initという風に単語間をハイフンで繋ぎます。
  • 8行目:Sentryでは、グループ関連のデータを処理するには、getGroupProvider()で、インスタンス化してから、メソッドチェーンで処理を記述します。
  • 59行目:上記と同様にユーザー関連のデータを処理するには、getUserProvider()で、インスタンス化します。

2. ルーターにRESTフルコントローラを登録します。

laravel/app/routes.php

Route::controller('setup','SetupController');

3. ビューファイルを下記のように作成します。

laravel/app/views/setup/index.blade.php

<!doctype html>
 <html>
 <head>
 <meta charset="utf-8">
 <title>Setup</title>
 </head>
<body>
 {{HTML::link('setup/sentry-init','初期グループ及びユーザーの作成')}}
 </body>
 </html>

4. ブラウザから直接アドレスを入力してもいいのですが、せっかく作成したビューファイル(setup/index.blade.php)から、リンクをクリックします。

5. phpMyAdminを確認すると下記のようにグループとユーザーが作成されています。

laravel06

 

laravel007

 

基本グループと初期ユーザーを作成しましたので、次回は、Loginコントローラを作成したいと思います。

 

Sentry2の導入

Senty2はLaravel4で使用できる高機能認証パッケージでLaravel以外にFuelPHP、CodeIgntiterでも使用できます。パッケージの依存管理は、composerで行っていますので、composer.jsonにパッケージを追加記述して、composer updateを実行し、プロバイダーの追加とエイリアスの追加を行えば、使用できるようになります。

詳細は、Winroad徒然草の「Laravel4でSentry2パッケージの追加」、「Sentry2でデータベーステーブルの作成」を参照していただければ分かると思いますが、、簡単に手順を記載しておきます。

  1. composer.jsonに追加記述
    "cartalyst/sentry":"2.0.*"
  2. composer updateの実行
    laravelをインストールしているルートフォルダ(ディレクトリ)へ移動して、コマンドプロンプトからcomposer updateを実行します。
  3. app/config/app.phpにプロバイダーの追加記述
    環境ファイルapp.phpの115行目にプロバイダーを追加します。

    'Cartalyst\Sentry\SenryServiceProvider',
  4. Senty::メソッド名で、使用できるようにするために同じくapp.phpの180行目にエイリアスを追加します。
    'Sentry'=>'Cartalyst\Sentry\Facades\Laravel\Sentry',
  5. データベーステーブルの作成
    ルートフォルダ(ディレクトリ)へ移動して、Artisanコマンドで、データベーステーブルを作成します。

    php artisan migrate --package=cartalyst/sentry

    ※packageの前のハイフォンは2個です。
    ※Artisanコマンドが初めての方は、先にmigrationsテーブルを作成して下さい。

    php artisan migrate:install
  6. 基本となるのグループを作成します。※最低AdninistratorsグループとUsersグループは作成した方がいいと思います。
  7. Adminユーザーを作成します。
  8. Loginコントローラーの作成
    ルーターでログイン用のロジックを作成してもいいのですが、Login関連のメソッドを作成するために、Loginコントローラを作成します。
  9. Loginフィルターの作成
    ログインしたユーザーによって閲覧できるサイトを振り分けるためにLoginフィルターを作成します。

以上の手順で作成していきたいと思いますが、1~7迄は、WinRoad徒然草でご紹介していますので、より実践的に使用するためにLoginコントローラの作成から次回以降、ご紹介していきたいと思います。