基於 JWT-Auth 實現 API 驗證
基於 JWT-Auth 實現 API 驗證
需要提及的幾點:
-
使用session存在的問題:
-
session和cookie是為了解決http無狀態的方案。session是用戶保存在服務器中的狀態信息,cookie中則保存jsessionId,請求服務器時,服務器讀取jsessionId從而確定用戶的身份信息,而session+cookie用在restful接口上破壞了其“無狀態”的特性,session運行時都是保存在內存中,而隨着認證用戶的增多,服務端的開銷會明顯增大。這也是restful最致力於通過“無狀態”解決的問題。如果使用session,那么restful也就沒有什么意義了
-
session降低了系統擴展性。用戶認證之后,服務端做認證記錄,如果認證的記錄被保存在內存中的話,這意味着用戶下次請求還必須要請求在這台服務器上,這樣才能拿到授權的資源,這樣在分布式的應用上,相應的限制了負載均衡器的能力。這也意味着限制了應用的擴展能力
-
cookie不安全,很容易導致跨站請求偽造攻擊(CSRF)
-
-
token存在的問題:
-
如,如何確定token的過期時間?如果token的有效期過短,很容易造成用戶用着用着就掉線的情況,降低用戶體驗。但目前看來好像並沒有太好的辦法,只能盡量延長token的有效期,或者每隔一次前端自動向服務端請求一次token
-
-
基於 JWT-Auth 的 token 驗證體系
-
運行軟件版本
-
laravel 5.7
-
-
安裝 JWT-Auth 擴展包
composer require tymon/jwt-auth "0.5.*"
或者可以
"require": {
"tymon/jwt-auth": "0.5.*"
" }
composer update
-
安裝完后在配置文件
config/app.php
中添加注冊服務提供者和別名:...
'providers' => [
...
Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
]
...
'aliases' => [
...
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
] -
發布資源配置
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"
-
運行以下命令生成密鑰
key
在生成的config/jwt.php
中// 如果運行后報錯,提示ERROR:Method Tymon\JWTAuth\Commands\JWTGenerateCommand::handle() does not exist,將vendor\tymon\jwt-auth\src\Commands\JWTGenerateCommand.php文件中的 fire() 方法修改為 handle()即可正常生成秘鑰
php artisan jwt:generate -
編輯
app/Http/Kernel.php
添加jwt.auth
和jwt.refresh
到應用路由中間件數組:protected $routeMiddleware = [
...
'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
// 'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class,
]; -
JWTAuth
自身中間件\Tymon\JWTAuth\Middleware\GetUserFromToken
中包含了對生成token的各類情況的驗證,以及異常的拋出。下面是其底層驗證類GetUserFromToken::class
:// file_path : vendor\tymon\jwt-auth\src\Middleware\GetUserFromToken.php
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tymon\JWTAuth\Middleware;
use Tymon\JWTAuth\Exceptions\JWTException; //驗證異常類
use Tymon\JWTAuth\Exceptions\TokenExpiredException;//token過期異常驗證類
class GetUserFromToken extends BaseMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, \Closure $next)
{
if (! $token = $this->auth->setRequest($request)->getToken()) {
return $this->respond('tymon.jwt.absent', 'token_not_provided', 400);
}
try {
$user = $this->auth->authenticate($token);
} catch (TokenExpiredException $e) {
return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]);
} catch (JWTException $e) {
return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]);
}
if (! $user) {
return $this->respond('tymon.jwt.user_not_found', 'user_not_found', 404);
}
$this->events->fire('tymon.jwt.valid', $user);
return $next($request);
}
}其中,調用的
respond
的方法在vendor\tymon\jwt-auth\src\Middleware\BaseMiddleware.php
文件中/**
* Fire event and return the response.
*
* @param string $event
* @param string $error
* @param int $status
* @param array $payload
* @return mixed
*/
protected function respond($event, $error, $status, $payload = [])
{
$response = $this->events->fire($event, $payload, true);
return $response ?: $this->response->json(['error' => $error], $status);
}可看到,當出現異常需要返回錯誤信息時,會連帶返回一個
fire event
的警告事件對象,這里不做詳述。 -
由底層代碼中,可以了解到,我們如果想自定義自己所需要的驗證方法,可以將這
GetUserFromToken::class
內容復制到我們自己自定義的中間件中。比如:-
創建自定義的驗證中間件
App\Http\Middleware\JwtAuth.php
php artisan make:middleware JwtAuth
-
全部復制到自定義的中間件中后,校驗下中間件中需要的類是否應用完全,命名空間是否正確等等,檢查無誤后根據需要自行定義需要的驗證功能。
// demo
namespace App\Http\Middleware;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Closure;
use Tymon\JWTAuth\Middleware\BaseMiddleware;
class JwtAuth extends BaseMiddleware
{
public function handle($request, \Closure $next)
{
if (! $token = $this->auth->setRequest($request)->getToken()) {
return $this->respond('tymon.jwt.absent', 'token_not_provided', 400);
}
try {
$user = $this->auth->authenticate($token);
} catch (TokenExpiredException $e) {
return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]);
} catch (JWTException $e) {
return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]);
}
if (! $user) {
return $this->respond('tymon.jwt.user_not_found', 'user_not_found', 404);
}
$this->events->fire('tymon.jwt.valid', $user);
return $next($request);
}
} -
定義完成后將自定義的中間件放入
app\Http\Kernel.php
的中間件數組中。protected $routeMiddleware = [
...
//'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
'jwt.auth_self' => \App\Http\Middleware\JwtAuth::class
]; -
添加好后,即可在
routes/api.php
中對需要控制的api路由進行驗證控制Route::group(['middleware'=>'jwt.auth_self'],function(){
// 需要控制的api路由
// ... code
});
-
-
我們現在可以對請求來的路由進行token的驗證,那么接下來我們就需要生成這個token,讓后續訪問中間件中的請求路由都攜帶這個token就能實現驗證。這里要提一下在安裝
JWT-Auth
過程中生成的配置文件config/jwt.php
<?php
return [
...
/*
|--------------------------------------------------------------------------
| User Model namespace
|--------------------------------------------------------------------------
|
| Specify the full namespace to your User model.
| e.g. 'Acme\Entities\User'
|
*/
// 設置你的用戶model,默認為laravel自帶的 User model
'user' => 'App\User',
]如果需求需要,可在配置文件中修改用戶
mode
,但配置的model
中需要引用Illuminate\Foundation\Auth\User as Authenticatable
,並繼承,寫法和User model
一致 -
具體的請求登錄以及獲取token信息,登出等功能實現,可參考此文章
Laravel 5 中使用 JWT(Json Web Token) 實現基於API的用戶認證,這里簡單提及下常用到的有關其token的方法
<?php
namespace App\Http\Controller;
use Tymon\JWTAuth\JWTAuth;
class Auth{
public function test (JWTAuth $JWTAuth){
// 獲取請求攜帶中的token
$token = $JWTAuth -> getToken();
// 獲取token中的用戶信息
$user = $JWTAuth -> parseToken() -> authenticate();
// 銷毀此次生成的token
$JWTAuth->setToken( $JWTAuth->getToken() )->invalidate();
// 自定義生成token,如果需要
$JWTAuth->setToken('foo.bar.baz');
}
}