目錄
jwt概述:
JWT(JSON Web Token)是一個非常輕巧的規范。這個規范允許我們使用JWT在用戶和服務器之間傳遞安全可靠的信息。一個JWT實際上就是一個字符串,它由三部分組成,頭部、載荷與簽名。
1、安裝jwt
安裝地址:https://packagist.org/packages/tymon/jwt-auth
composer require tymon/jwt-auth
2.注冊服務提供者和別名
安裝完成后,在配置文件 config/app.php 中注冊服務提供者和別名:
'providers' => [ Tymon\JWTAuth\Providers\LaravelServiceProvider::class ] 'aliases' => [ 'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class, ]
3、發布配置文件
在你項目根目錄運行如下命令,生成config\ jwt.php 的配置文件:
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
4.在發布的配置中生成key:
此命令會在你的 .env 文件中新增一行 JWT_SECRET=secret。
php artisan jwt:secret
5.配置 api的驗證模式
在 config/auth.php 文件中,你需要將 guards中api['driver ']的值更新為 jwt:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ],
6、修改Model
如果需要使用 jwt-auth 作為用戶認證,我們需要對app/User模型進行修改,修改后的代碼如下:
<?php namespace App; use Tymon\JWTAuth\Contracts\JWTSubject; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable implements JWTSubject { use Notifiable; /** * The attributes that are mass assignable. *要驗證的字段 * @var array */ protected $fillable = [ 'openid', 'uuid', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get the identifier that will be stored in the subject claim of the JWT. * * @return mixed */ public function getJWTIdentifier() { return $this->getKey(); } /** * Return a key value array, containing any custom claims to be added to the JWT. * * @return array */ public function getJWTCustomClaims() { return []; } }
注:此處有一個需要注意的地方如下圖
7、 配置項詳解
文件位置:config/jwt.php,jwt.php中的各種詳解
<?php return [ /* |-------------------------------------------------------------------------- | JWT Authentication Secret |-------------------------------------------------------------------------- | 用於加密生成 token 的 secret */ 'secret' => env('JWT_SECRET'), /* |-------------------------------------------------------------------------- | JWT Authentication Keys |-------------------------------------------------------------------------- | 如果你在 .env 文件中定義了 JWT_SECRET 的隨機字符串 | 那么 jwt 將會使用 對稱算法 來生成 token | 如果你沒有定有,那么jwt 將會使用如下配置的公鑰和私鑰來生成 token */ 'keys' => [ /* |-------------------------------------------------------------------------- | Public Key |-------------------------------------------------------------------------- | 公鑰 */ 'public' => env('JWT_PUBLIC_KEY'), /* |-------------------------------------------------------------------------- | Private Key |-------------------------------------------------------------------------- | 私鑰 */ 'private' => env('JWT_PRIVATE_KEY'), /* |-------------------------------------------------------------------------- | Passphrase |-------------------------------------------------------------------------- | 私鑰的密碼。 如果沒有設置,可以為 null。 */ 'passphrase' => env('JWT_PASSPHRASE'), ], /* |-------------------------------------------------------------------------- | JWT time to live |-------------------------------------------------------------------------- | 指定 access_token 有效的時間長度(以分鍾為單位),默認為1小時,您也可以將其設置為空,以 | 產生永不過期的標記 */ 'ttl' => env('JWT_TTL', 60), /* |-------------------------------------------------------------------------- | Refresh time to live |-------------------------------------------------------------------------- | 指定 access_token 可刷新的時間長度(以分鍾為單位)。默認的時間為 2 周。 | 大概意思就是如果用戶有一個 access_token,那么他可以帶着他的 access_token | 過來領取新的 access_token,直到 2 周的時間后,他便無法繼續刷新了,需要重新登錄。 */ 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), /* |-------------------------------------------------------------------------- | JWT hashing algorithm |-------------------------------------------------------------------------- | 指定將用於對令牌進行簽名的散列算法。 */ 'algo' => env('JWT_ALGO', 'HS256'), /* |-------------------------------------------------------------------------- | Required Claims |-------------------------------------------------------------------------- | 指定必須存在於任何令牌中的聲明。 */ 'required_claims' => [ 'iss', 'iat', 'exp', 'nbf', 'sub', 'jti', ], /* |-------------------------------------------------------------------------- | Persistent Claims |-------------------------------------------------------------------------- | 指定在刷新令牌時要保留的聲明密鑰。 */ 'persistent_claims' => [ // 'foo', // 'bar', ], /* |-------------------------------------------------------------------------- | Blacklist Enabled |-------------------------------------------------------------------------- | 為了使令牌無效,您必須啟用黑名單。如果您不想或不需要此功能,請將其設置為 false。 */ 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true), /* | ------------------------------------------------------------------------- | Blacklist Grace Period | ------------------------------------------------------------------------- | 當多個並發請求使用相同的JWT進行時, | 由於 access_token 的刷新 ,其中一些可能會失敗 | 以秒為單位設置請求時間以防止並發的請求失敗。 */ 'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0), /* |-------------------------------------------------------------------------- | Providers |-------------------------------------------------------------------------- | 指定整個包中使用的各種提供程序。 */ 'providers' => [ /* |-------------------------------------------------------------------------- | JWT Provider |-------------------------------------------------------------------------- | 指定用於創建和解碼令牌的提供程序。 */ 'jwt' => Tymon\JWTAuth\Providers\JWT\Namshi::class, /* |-------------------------------------------------------------------------- | Authentication Provider |-------------------------------------------------------------------------- | 指定用於對用戶進行身份驗證的提供程序。 */ 'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class, /* |-------------------------------------------------------------------------- | Storage Provider |-------------------------------------------------------------------------- | 指定用於在黑名單中存儲標記的提供程序。 */ 'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class, ], ];
8、自定義RefreshToken 中間件
實現效果:提供賬號密碼前來登錄。如果登錄成功,那么我會給前端返回一個 token ,設置在 header 中以請求需要用戶認證的路由。
如果用戶的令牌如果過期了,可以暫時通過此次請求,並在此次請求中刷新該用戶的 token,最后在響應頭中將新的 token 返回給前端,這樣子可以無痛的刷新 token ,用戶可以獲得一個很良好的體驗,所以開始動手寫代碼。無限刷新默認時間是兩周。
php artisan make:middleware RefreshToken
<?php namespace App\Http\Middleware; use Closure; use Auth; use Tymon\JWTAuth\Http\Middleware\BaseMiddleware; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenExpiredException; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; class RefreshToken extends BaseMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { // 檢查此次請求中是否帶有 token,如果沒有則拋出異常。 $this->checkForToken($request); // 使用 try 包裹,以捕捉 token 過期所拋出的 TokenExpiredException 異常 try { // 檢測用戶的登錄狀態,如果正常則通過 if ($this->auth->parseToken()->authenticate()) { return $next($request); } throw new UnauthorizedHttpException('jwt-auth', '未登錄'); } catch (TokenExpiredException $exception) { // 此處捕獲到了 token 過期所拋出的 TokenExpiredException 異常,我們在這里需要做的是刷新該用戶的 token 並將它添加到響應頭中 try { // 刷新用戶的 token $token = $this->auth->refresh(); // 使用一次性登錄以保證此次請求的成功 Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']); } catch (JWTException $exception) { // 如果捕獲到此異常,即代表 refresh 也過期了,用戶無法刷新令牌,需要重新登錄。 throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage()); } } // 在響應頭中返回新的 token return $this->setAuthenticationHeader($next($request), $token); } }
9、注冊RefreshToken 中間件
在app/Http/Kernel.php 添加如下代碼:
protected $routeMiddleware = [ 'refresh' => \App\Http\Middleware\RefreshToken::class, ]
10、更新異常處理的handler
由於我們構建的是 api 服務,所以我們需要更新一下 app/Exceptions/Handler.php 中的 render方法,自定義處理一些異常。
<?php namespace App\Exceptions; use Exception; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Validation\ValidationException; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; class Handler extends ExceptionHandler { /** * A list of the exception types that are not reported. * * @var array */ protected $dontReport = [ // ]; /** * A list of the inputs that are never flashed for validation exceptions. * * @var array */ protected $dontFlash = [ 'password', 'password_confirmation', ]; /** * Report or log an exception. * * This is a great spot to send exceptions to Sentry, Bugsnag, etc. * * @param \Exception $exception * @return void */ public function report(Exception $exception) { parent::report($exception); } /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Exception $exception * @return \Illuminate\Http\Response */ public function render($request, Exception $exception) { // 參數驗證錯誤的異常,我們需要返回 400 的 http code 和一句錯誤信息 if ($exception instanceof ValidationException) { return response(['error' => array_first(array_collapse($exception->errors()))], 400); } // 用戶認證的異常,我們需要返回 401 的 http code 和錯誤信息 if ($exception instanceof UnauthorizedHttpException) { return response($exception->getMessage(), 401); } return parent::render($request, $exception); } }
11、添加新的控制器
創建控制器:
php artisan make:controller AuthController
添加如下代碼:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use App\Transformers\UserTransformer; class AuthController extends Controller { /** * Get a JWT token via given credentials. * * @param \Illuminate\Http\Request $request * * @return \Illuminate\Http\JsonResponse */ public function login(Request $request) { // 驗證規則,由於業務需求,這里我更改了一下登錄的用戶名,使用手機號碼登錄 $rules = [ 'email' => [ 'required', 'exists:users', ], 'password' => 'required|string|min:6|max:20', ]; // 驗證參數,如果驗證失敗,則會拋出 ValidationException 的異常 $params = $this->validate($request, $rules); // 使用 Auth 登錄用戶,如果登錄成功,則返回 201 的 code 和 token,如果登錄失敗則返回 return ($token = Auth::guard('api')->attempt($params)) ? response(['token' => 'bearer ' . $token], 201) : response(['error' => '賬號或密碼錯誤'], 400); } /** * 處理用戶登出邏輯 * * @return \Illuminate\Http\JsonResponse */ public function logout() { Auth::guard('api')->logout(); return response(['message' => '退出成功']); } }
12、添加路由
在 routes/api.php 文件中,添加路由:
Route::group(['prefix'=>'auth'],function($router) { $router->match(['post','get'],'login', 'AuthController@login'); $router->post('logout', 'AuthController@logout'); }); Route::middleware('refresh')->group(function($router) { $router->get('profile','UserController@profile'); });
13、在數據庫中添加數據
DB::table('users')->insert( ['email' => 'john@example.com', 'password' => bcrypt('123456')] );
注:此處密碼需用函數處理,因為Auth::attempt($params)是使用bcrypt()加密驗證的,否則會一直返回false
14、請求接口
15、請求結果
注:若郵箱在數據庫中不存在,則也會返回false,因為驗證規則里面要求的是必須在數據表中存在,自己也可以修改
轉載:https://blog.csdn.net/robin_sky/article/details/108230611 王老板