laravel5.1でミドルウェア機能を使って、独自Basic認証を実装してみた。
参考URL: http://qiita.com/sogawa@github/items/c6ac3e9f46447aa32668
Q1, そもそもミドルウェアって何?
A1, URLをリクエストされた時に、route.phpで各種コントローラーに処理を振り分ける前(後も出来る)に処理を入れたい時に使う。
ユーザ認証とか、全ページで同じ処理をしたい時に使うと便利!
1 2 |
# app\Http\Middleware\TestMiddleware.phpというファイルが生成される。 php artisan make:middleware TestMiddleware |
artisanで生成されたてで何も処理は書かれていないMiddleware
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php namespace App\Http\Middleware; use Closure; class TestMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { return $next($request); } } |
Q2, ミドルウェアの使い方は?
A2, 以下の通り。
a, まず、生成されたファイル内のhandle関数内に、行いたい処理を書きます。
b, 次にlaravelに登録(app/Http/Kernel.php)します。
1 2 3 4 5 6 |
// 特に指定しなくてもプロジェクト全体のリクエストでコールされるglobal protected $middleware = ['App\Http\Middleware\TestMiddleware', ]; // route.phpでRoute::get('/test', ['middleware' => 'test', function() {のように、URL毎に明示的に指定する // こちらでは省略名も定義する。 protected $routeMiddleware = ['test' => 'App\Http\Middleware\TestMiddleware', ]; |
グローバルl($middleware)で定義しちゃうと、個別($routeMiddleware)に指定する意味がなくなるので、どちらかだけに登録する。
Q3, とりあえず、グローバル($middleware)の方に登録してみたけど、後はどうしたらいいの?
A3, その時点で、どのURLをリクエストしてもTestMiddlewareの処理が走るようになります。
以下のように記述すると、簡易的なアクセスログが作れます。
1 2 3 4 5 6 7 |
public function handle($request, Closure $next) { // storage\logs\laravel.logに、パスを出力 \Log::info('Requested PATH /' . $request->path()); return $next($request); } |
http://localhost/project_name/public/sample にアクセスすると、storage\logs\laravel.logに出力される
[2016-10-21 01:33:10] local.INFO: Requested PATH /sample
[2016-10-21 01:33:13] local.INFO: Requested PATH /sample
Q4, もっと他に使い道は?
A4, laravelデフォルトのBasic認証(auth.basic)より簡単に出来る(データベースを使わない)
これで、どのページでもBasic認証がかかる様になった。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public function handle($request, Closure $next) { // PHPによるBasic認証 http://qiita.com/mpyw/items/dc2cb3632370389d700e switch (true) { case !isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']): case $_SERVER['PHP_AUTH_USER'] !== 'test': case $_SERVER['PHP_AUTH_PW'] !== 'test': header('WWW-Authenticate: Basic realm="Enter username and password."'); header('Content-Type: text/plain; charset=utf-8'); die('このページを見るにはログインが必要です'); } header('Content-Type: text/html; charset=utf-8'); return $next($request); } |
Q5, Basic認証のログイン用アカウントを、個人テーブル・会社テーブルの両方のどちらのID/PASSでもOKにしたい!
A5, 以下の通りで実装出来た!
a, MySQL側でCreate Viewを使い、両方のテーブルを結合させておく
1 2 |
#通常のテーブルと同じようにファイル生成して、migrateの時にCREATE VIEW php artisan make:migration create_logins_table |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateLoginsTable extends Migration { public function up() { DB::statement( 'DROP VIEW IF EXISTS logins' ); DB::statement( "CREATE VIEW logins AS select id as id, password as password, name as name, mail_address as email, 'person' as kind from persons union select id as id, password as password, name as name, mail_address as email, 'company' as kind from companies " ); } public function down() { DB::statement( 'DROP VIEW IF EXISTS logins' ); } } |
b, Loginモデルを作っておく(コーディングは何もしなくても良い。親クラスの継承メソッドでアクセスできるようになる)
1 2 3 4 |
php artisan make:model Login # Basic認証用に、新しくミドルウェアを生成。 php artisan make:middleware BasicLoginMiddleware |
c, Kernel.phpにもglobal登録
1 2 3 4 5 |
// 特に指定しなくてもプロジェクト全体のリクエストでコールされるglobal protected $middleware = [ 'App\Http\Middleware\TestMiddleware', //最初に作ったmiddleware 'App\Http\Middleware\BasicLoginMiddleware', // Basic認証用 ]; |
d, Basic認証の処理を実装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<?php namespace App\Http\Middleware; use Closure; use App\Login; // Create Viewへのアクセス用 class BasicLoginMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { // PHPによるBasic認証 http://qiita.com/mpyw/items/dc2cb3632370389d700e // Basic認証ダイアログから入力されたID/PASS(最初は未入力なので空文字を代入) $id = (int) isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : ''; $password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; //最初に適合したIDレコードを取得 $login = Login::where('id', $id)->first(); // ハッシュ化済みパスワードのソルトを使って、受け取ったパスワードをハッシュ化後に比較 // 認証できなければ、何度でもBasic認証ダイアログを表示! if(!\Hash::check($password, $login['password'])){ header('WWW-Authenticate: Basic realm="Enter username and password."'); header('Content-Type: text/plain; charset=utf-8'); die('このページを見るにはログインが必要です'); } // 有効なアカウントなら、先に進める header('Content-Type: text/html; charset=utf-8'); return $next($request); } } |
e, route.phpに実装して、動作を確認してみる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
use App\Login; // Basic認証をパスしていないと見れない! Route::get('/test', function () { return 'this is The test for Basic Authentication<br>' . url('/profile'); }); // Basic認証のIDから、ログインユーザの詳細情報を取得して表示する Route::get('/profile', function () { // Basic認証はパスしているはずだから、IDのみでチェック $login = Login::where('id', $_SERVER['PHP_AUTH_USER'])->first(); //個人IDでログインされたら if($login->kind == 'person'){ $disp_str = "ログイン種別=個人<br>". "契約者ID=" . $login->id . '<br>'. "契約者名=" . $login->name . '<br>'. "メールアドレス=" . $login->email . '<br>'; // 会社IDでログインされたら }else{ $disp_str = "ログイン種別=会社<br>". "会社ID=" . $login->id . '<br>'. "会社名=" . $login->name . '<br>'. "メールアドレス=" . $login->email . '<br>'; } $disp_str .= '<br>Basic認証ログアウト用 ' . url('/logout'); // 画面にログイン情報を表示する return $disp_str; }); // Basic認証ダイアログを表示させてクリアする Route::get('/logout', function () { header('WWW-Authenticate: Basic realm="Enter username and password."'); header('Content-Type: text/plain; charset=utf-8'); die('再ログイン ' . url('/profile')); }); // Basic認証が続いているか、どうか? Route::get('/test2', function () { return 'test2'; }); |
Basic認証ダイアログが出る。個人・会社のどちらかのテーブルに登録されているID/PASSなら認証される。
http://localhost/project_name/public/test
ログインIDから、ユーザ情報を取得して表示する
http://localhost/project_name/public/profile
わざとBasic認証ダイアログを表示させ、認証クリアする
ID/PASSに何を入力しても認証されない。
http://localhost/project_name/public/logout
特に意味はないが、Basic認証が途中で切れていないかチェック用
http://localhost/project_name/public/test2