
Middleware & Session處理
Middleware & Session處理
Middleware & Session處理, 相信很多人的專案都有類似功能:我們希望使用者在使用某個功能或是頁面時是有時間限制的,若超過時間都沒有提他動作時再訪問時就會被踢出,或是需要重新驗證。一般筆者再處理的時候都是在controller
內處理,今天發現同事使用Middleware 去處理這方面的問題,好像也是個不錯的處理方式,做個筆記來記錄一下
這邊用個實例來模擬比較有記憶點。
有一個提供查詢轉帳明細的頁面,但是帳務明細是相當敏感的資料,所以我們需要輸入密碼才可以進入轉帳。我們把名稱都定義為Account
,每個 Account在看帳務明細時需要輸入另外的密碼code
,在進入到帳戶明細中後,若五分鐘沒有動靜的話,再執行任務時需再輸入一次code
。
需先新增帳戶密碼,database\migrations
新增TIMESTAMP_create_accounts_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateAccountsTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('accounts', function(Blueprint $table)
{
$table->increments('id');
$table->string('account')->comment('帳戶名稱');
$table->string('code')->comment('帳戶密碼');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('accounts');
}
}
並跑
php artisan migrate
後去資料庫內生資料出來。我是比較喜歡用tinker 搭配 factory的作法處理。
主要的程式碼分成兩個部分:Account.php(Middleware) 及 Route和AccountController,我們先來定義路由。
App\routes\web
//...
Route::group(['prefix' => 'accounts', 'as' => 'account', 'middleware' => 'account'], function () {
Route::get('/code', 'AccountLoginController@showLoginForm')->name('.loginform');//登入頁
Route::post('/code', 'AccountLoginController@login')->name('.login');//驗證密碼
Route::get('/', 'AccountController@index')->name('.index');//資料主頁,我們這邊不實作主頁內容,主要著重在middleware
//...
接下來是AccountLoginController:我們這邊先定義Middleware的幾個功能 (TDD的概念?)
isVerified()
:確認是否已驗證,若已驗證則轉跳到主頁面。redirect()
:回到主頁面。verify($code)
:驗證登入內容。應該要比對Account
密碼。
<?php
namespace App\Http\Controllers\Account;
use App\Http\Controllers\Controller;
use App\Http\Middleware\Account;
class AccountLoginController extends Controller
{
private $account;
public function __construct(Account $account)
{
$this->account = $account;
}
public function showLoginForm()
{
if ($this->account->isVerified()) {
return $this->account->redirect();
}
return view('account/login');
}
public function login(Request $request)
{
if ($this->account->verify($request->code)) {
return $this->account->redirect();
}
return back()->withErrors(['msg' => '驗證失敗']);
}
}
最後是最重要的Middleware。
Middleware
在App\Http\Middleware
內新增一個Account.php
//Account.php
<?php
namespace App\Http\Middleware;
use Closure;
class Account
{
const SESSION = 'code';//存放在Session的Key值
const PREVIOUS = 'previous';//處理無限跳轉問題,可參考inExceptArray()
const REDIRECT_TO = 'account.index';//轉跳的主頁
const EXPIRED_MINS = '+ 5min'; //輸入Session過期時間
protected $except = [//當使用 $request->is()的時候帶入,可確認是否來自accounts/code,避免無限重導
'accounts/code'
];
//在每次進入middleware時,若有驗證過則表示已經登入過,會自動刷新驗證時間並通過,若是輸入登入頁面進來的,可以通過middleware,其他在middleware底下的頁面,即先儲存路由,若登入成功則取出路由並跳轉到該路由。
public function handle($request, Closure $next)
{
if ($this->isVerified()) {
$this->setVerified();
return $next($request);
} elseif ($this->inExceptArray($request)) {
return $next($request);
}
session([self::PREVIOUS => \Route::currentRouteName()]);
return redirect(route('account.loginform'));
}
//這邊利用Auth的方式去取得code,且用Hash的方式比對,所以code 在存入table 前要先經過Hash!
public function verify($code)
{
if (\Hash::check($code, \Auth::user()->code)) {
$this->setVerified();
return true;
}
}
//儲存驗證的expire time 到session
public function setVerified($min = null)
{
if (!$min) {
$min = self::EXPIRED_MINS;
}
session([self::SESSION => strtotime($min)]);
}
//從session取值,判斷是否有驗證
public function isVerified()
{
$timestamp = session(self::SESSION);
return $timestamp and $timestamp >= time();
}
//轉跳功能
public function redirect()
{
$route = session(self::PREVIOUS);
if (!$route) {
$route = self::REDIRECT_TO;
}
return redirect(route($route));
}
//由Request確認當前路由是否為登入畫面,若是則回傳True(到Handle時通過),若否則回傳False(到 Handle時儲存路由,並跳轉到登入畫面,登入成功後跳轉到該路由)
protected function inExceptArray($request)
{
foreach ($this->except as $except) {
if ($request->is($except)) {//判斷$request是否有該path
return true;
}
}
return false;
}
}
關於 Except的重導問題可以參考Illuminate\Foundation\Http\Middleware
\VerifyCsrfToken`應該會比較好理解。
另外記得要註冊 middleware 否則routes內無法使用
App\Http\Kernel.php
//...
protected $routeMiddleware = [
//...
'account' => \App\Http\Middleware\Account::class,
];
View的部分相對簡單:
<div id="wrapper">
<div class="container">
<div class="row" style="margin-top:20px">
<div class="col-xs-12 col-sm-8 col-md-5 col-sm-offset-2 col-md-offset-3">
<form id="loginForm" method="POST" action="{{ route('account.login') }}">
{{ csrf_field() }}
<h4 class="text-center"><i class="fa fa-user" aria-hidden="true"></i> 帳戶驗證</h4>
<hr class="colorgraph">
<div class="form-group">
<input type="password" name="code" class="form-control input-xs" placeholder="帳戶驗證碼" data-parsley-required >
<p class="help-block">驗證後有效時間為 5 分鐘</p>
</div>
@if ($errors->has('msg'))
<div class="alert alert-danger" role="alert">
{{ $errors->first('msg') }}
</div>
@endif
<hr class="colorgraph">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<button type="submit" class="btn btn-primary btn-block">驗 證</button>
</div>
</div>
</form>
</div>
<!-- /.col-xs-12 col-sm-8 col-md-5 col-sm-offset-2 col-md-offset-3 -->
</div>
<!-- /.row -->
</div>
<!-- /.container -->
</div>
<!-- /#wrapper -->
這樣應該就算簡單完成用 Middleware做Session的驗證了。