先後端分離,使用token的方式校驗用戶信息,我選擇了jwt,使用的教程在網上能夠找到不少,不作介紹。php
這裏說明一個使用過程當中,最重要的的一個環節刷新token帶來的問題。前端
業務要達到的目標:git
用戶登陸一次以後,前端保存token,後面每次向後端請求的時候,header都帶上authorization信息,後端從請求中解析token,根據token驗證用戶信息,返回相應的信息。github
相信大部分看過文檔並開始使用的同窗都已經走通到這裏了,下面是入坑的開始:redis
1. 產品要求json
半個月內免登錄,這裏就要使用到了refreshToken了,jwt設計思想很到位:設置發給前端的token一個有效期,好比2個小時,2個小時候前端發來的token就會失效,這個時候咱們根據發來的token判斷下,若是這個token在2個小時外,並在刷新token的有效期內(好比半個月內),那麼咱們在給前端返回數據的時候返回一個新token,前端接到這個token存儲起來,當再次請求的時候,發送新的token,如此周而復始,只要你在半個月內沒有間斷去進入系統,那麼徹底不須要去進行登陸的操做。
2. 問題
1)如何將新的token發給前端比較好?後端
這個問題答案簡單,在response 的header中設置authorization。 關鍵點:後端通常使用的域名是二級域名好比個人是api.xx.com,會和前端產生一個跨域的影響,請記得必定要設置 `$response->headers->set('Access-Control-Expose-Headers', 'Authorization');` 設置跨域的時候還要設置一個Cache-Control,這個東西出現的問題真的是莫名其妙,坑了我好久.. `$response->headers->set('Cache-Control', 'no-store'); // 無的話會致使前端從緩存獲取頭token`
2) 通常是在中間件中刷新token,當前請求繼續走,如何在controller中須要根據token調取登陸用戶信息?api
一會兒可能沒說明問題,簡單理解爲:token已經刷新了,那麼當前token確定失效了,繼續在controller利用請求中的token確定會報token失效的錯誤,這裏須要將新token帶到後面的程序處理中,我這裏更改了當前請求頭,將newToken替換了request header中的Authorization。
3) 併發請求。也就是2個小時候以後,同一個頁面發來了2個請求,這個很正常,好比一個請求列表數據,一個請求搜索的表單,由於token都已失效,那麼難道返回2個新的token回去?跨域
這個問題找了在github裏面看到了issue可是無人回答,jwt確定不會發兩個新的token回去的,那麼確定會有一個token不只是失效了,刷新當前token以後,產生新的token,舊token加入到了backlist中了,沒法使用,那麼另一個請求天然沒法成功。我這裏使用Redis解決的,將舊token做爲鍵,新token做爲值,設置一個30秒過時的時間。當第二個請求來的時候,已經知道token在backlist中了,咱們能夠去redis查詢下是否存在這麼個舊token,存在的話放行。
3. 關鍵中間件代碼緩存
<?php namespace App\Http\Middleware; use Closure; use JWTAuth; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenExpiredException; use Tymon\JWTAuth\Exceptions\TokenInvalidException; use Illuminate\Support\Facades\Redis; class GetUserFromToken { public function handle($request, Closure $next) { $newToken = null; $auth = JWTAuth::parseToken(); if (! $token = $auth->setRequest($request)->getToken()) { return response()->json([ 'code' => '2', 'msg' => '無參數token', 'data' => '', ]); } try { $user = $auth->authenticate($token); if (! $user) { return response()->json([ 'code' => '2', 'msg' => '未查詢到該用戶信息', 'data' => '', ]); } $request->headers->set('Authorization','Bearer '.$token); } catch (TokenExpiredException $e) { try { sleep(rand(1,5)/100); $newToken = JWTAuth::refresh($token); $request->headers->set('Authorization','Bearer '.$newToken); // 給當前的請求設置性的token,以備在本次請求中須要調用用戶信息 // 將舊token存儲在redis中,30秒內再次請求是有效的 Redis::setex('token_blacklist:'.$token,30,$newToken); } catch (JWTException $e) { // 在黑名單的有效期,放行 if($newToken = Redis::get('token_blacklist:'.$token)){ $request->headers->set('Authorization','Bearer '.$newToken); // 給當前的請求設置性的token,以備在本次請求中須要調用用戶信息 return $next($request); } // 過時用戶 return response()->json([ 'code' => '2', 'msg' => '帳號信息過時了,請從新登陸', ]); } } catch (JWTException $e) { return response()->json([ 'code' => '2', 'msg' => '無效token', 'data' => '', ]); } $response = $next($request); if ($newToken) { $response->headers->set('Authorization', 'Bearer '.$newToken); } return $response; } }
一成天的時間耗在這裏了,實踐纔會發現問題,累並快樂着解決了^_^
=====================割了一了白了(2019)===================
關於第三個問題,當時寫的時候就感受很噁心,做者已經出了一個新版本(1.0.0-rc.1),config裏面多了一項配置'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 60)
當多個併發請求使用相同的JWT進行時,因爲 access_token 的刷新 ,其中一些可能會失敗,以秒爲單位設置請求時間以防止併發的請求失敗。
你們能夠享用這個新版,能夠很好解決這個問題。中間件更新了部分:
try { // 刷新token,超時刷新將會去catch $refresh = JWTAuth::parseToken()->refresh(); $user = $auth->authenticate($refresh); // 生成新token(上面的$refresh也是合法的刷新token,這裏若是直接用,2次刷新以後再沒法繼續得到,親測) $newToken = JWTAuth::fromUser($user); // 給當前的請求設置性的token,以備在本次請求中須要調用用戶信息 $request->headers->set('Authorization','Bearer '.$newToken); }