Laravel + jwt-auth 實現 API 跨域授權


在開發Api時,處理客戶端請求之前,需要對用戶進行身份認證,Laravel框架默認為我們提供了一套用戶認證體系,在進行web開發時,幾乎不用添加修改任何代碼,可直接使用,但在進行api開發時,需要我們自己去實現,並且Laravel框架默認提供的身份認證不是jwt的,需要在數據庫中增加api_token字段,記錄用戶認證token並進行身份校驗如果需要使用jwt,無需添加字段,需要借助三方庫來實現。

Token認證原理

  1. 客戶端發送認證信息 (一般就是用戶名 / 密碼), 向服務器發送請求
  2. 服務器驗證客戶端的認證信息,驗證成功之后,服務器向客戶端返回一個 加密的 token (一般情況下就是一個字符串)
  3. 客戶端存儲 (cookie, session, app 中都可以存儲) 這個 token, 在之后每次向服務器發送請求時,都攜帶上這個 token
  4. 服務器驗證這個 token 的合法性,只要驗證通過,服務器就認為該請求是一個合法的請求

JWT概述

token 只是一種思路,一種解決用戶授權問題的思考方式,基於這種思路,針對不同的場景可以有很多種的實現。而在眾多的實現中,JWT (JSON Web Token) 的實現最為流行.

JWT 這個標准提供了一系列如何創建具體 token 的方法,這些緣故方法和規范可以讓我們創建 token 的過程變得更加合理和效率.

比如,傳統的做法中,服務器會保存生成的 token, 當客戶端發送來 token 時,與服務器的進行比對,但是 jwt 的不需要在服務器保存任何 token, 而是使用一套加密 / 解密算法 和 一個密鑰 來對用戶發來的 token 進行解密,解密成功后就可以得到這個用戶的信息.

這樣的做法同時也增加了多服務器時的擴展性,在傳統的 token 驗證中,一旦用戶發來 token, 那么必須要先找到存儲這個 token 的服務器是哪台服務器,然后由那一台服務器進行驗證用戶身份。而 jwt 的存在,只要每一台服務器都知道解密密鑰,那么每一台服務器都可以擁有驗證用戶身份的能力.

這樣一來,服務器就不再保存任何用戶授權的信息了,也就解決了 session 曾出現的問題.

JWT 標准的 Token 有三個部分:header、payload、signature

客戶端收到這個 Token 以后把它存儲下來,下次向服務端發送請求的時候就帶着這個 Token 。服務端收到這個 Token ,然后進行驗證,通過以后就會返回給客戶端想要的資源。驗證的過程就是
根據傳過來的token再生成一下第三部分Signature,然后兩個比對一下,一致就驗證通過。

一、安裝及基礎配置

1. 使用 composer 安裝

# 建議使用1.0以上版本
composer require tymon/jwt-auth 1.*@rc

2. 進行一些配置

這里值得注意的是,有些文檔會說要添加 Tymon\JWTAuth\Providers\LaravelServiceProvider::class ,這只在 Laravel 5.4 及以下版本是必要的,更新的 Laravel 版本無需添加。

還有一些文檔說要添加 Tymon\JWTAuth\Providers\JWTAuthServiceProvider 這是很久以前的 JWT 版本的(大概 0.5.3 以前的版本)。

2.1 發布配置文件

# 這條命令會在 config 下增加一個 jwt.php 的配置文件
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

2.2 生成加密密鑰

# 這條命令會在 .env 文件下生成一個加密密鑰,如:JWT_SECRET=foobar
php artisan jwt:secret

2.3 更新你的模型

如果你使用默認的 User 表來生成 token,你需要在該模型下增加一段代碼

<?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;

    // Rest omitted for brevity

    /**
     * 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 [];
    }
}

2.4 注冊兩個 Facade

這兩個 Facade 並不是必須的,但是使用它們會給你的代碼編寫帶來一點便利。

config/app.php

'aliases' => [
        ...
        // 添加以下兩行
        'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth',
        'JWTFactory' => 'Tymon\JWTAuth\Facades\JWTFactory',
],

如果你不使用這兩個 Facade,你可以使用輔助函數 auth ()

auth () 是一個輔助函數,返回一個 guard,暫時可以看成 Auth Facade。

// 如果你不用 Facade,你可以這么寫
auth('api')->refresh();
// 用 JWTAuth Facade
JWTAuth::parseToken()->refresh();

兩個 Facede 常用可使用方法,可以看文章后面的附錄。

2.5 修改 auth.php

config/auth.php

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'jwt',        // 原來是 token 改成jwt
        'provider' => 'users',
    ],
],

2.6 注冊一些路由

注意:在 Laravel 下,route/api.php 中的路由默認都有前綴 api 。

Route::group([

    'prefix' => 'auth'

], function ($router) {

    Route::post('login', 'AuthController@login');
    Route::post('logout', 'AuthController@logout');
    Route::post('refresh', 'AuthController@refresh');
    Route::post('me', 'AuthController@me');

});

2.7 創建 token 控制器

php artisan make:controller AuthController

AuthController

值得注意的是 Laravel 這要用 auth('api')

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class AuthController extends Controller
{
    /**
     * Create a new AuthController instance.
     * 要求附帶email和password(數據來源users表)
     * 
     * @return void
     */
    public function __construct()
    {
          // 這里額外注意了:官方文檔樣例中只除外了『login』
          // 這樣的結果是,token 只能在有效期以內進行刷新,過期無法刷新
          // 如果把 refresh 也放進去,token 即使過期但仍在刷新期以內也可刷新
          // 不過刷新一次作廢
        $this->middleware('auth:api', ['except' => ['login']]);
          // 另外關於上面的中間件,官方文檔寫的是『auth:api』
          // 但是我推薦用 『jwt.auth』,效果是一樣的,但是有更加豐富的報錯信息返回
    }

    /**
     * Get a JWT via given credentials.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login()
    {
        $credentials = request(['email', 'password']);

        if (! $token = auth('api')->attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    /**
     * Get the authenticated User.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function me()
    {
        return response()->json(auth('api')->user());
    }

    /**
     * Log the user out (Invalidate the token).
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        auth('api')->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    /**
     * Refresh a token.
     * 刷新token,如果開啟黑名單,以前的token便會失效。
     * 值得注意的是用上面的getToken再獲取一次Token並不算做刷新,兩次獲得的Token是並行的,即兩個都可用。
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        return $this->respondWithToken(auth('api')->refresh());
    }

    /**
     * Get the token array structure.
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60
        ]);
    }
}

關於中間件

1.0 版本以上的 jwt-auth,中間件在服務提供者中已經定義了,所以不需要額外寫,按上面來即可。

下面是可用的中間件,第一二個功能一樣,但是第二個不會拋出錯誤,第三四個功能一樣,沒什么區別。

tymon\jwt-auth\src\Providers\AbstractServiceProvider.php

protected $middlewareAliases = [
    'jwt.auth' => Authenticate::class,
    'jwt.check' => Check::class,
    'jwt.refresh' => RefreshToken::class,
    'jwt.renew' => AuthenticateAndRenew::class,
];

自定義認證中間件

先來說明一下我想要達成的效果,我希望用戶提供賬號密碼前來登錄。如果登錄成功,那么我會給前端頒發一個 access _token ,設置在 header 中以請求需要用戶認證的路由。

同時我希望如果用戶的令牌如果過期了,可以暫時通過此次請求,並在此次請求中刷新該用戶的 access _token,最后在響應頭中將新的 access _token 返回給前端,這樣子可以無痛的刷新 access _token ,用戶可以獲得一個很良好的體驗,所以開始動手寫代碼。

執行如下命令以新建一個中間件:

php artisan make:middleware RefreshToken

中間件代碼如下:

RefreshToken.php

<?php

namespace App\Http\Middleware;

use Auth;
use Closure;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

// 注意,我們要繼承的是 jwt 的 BaseMiddleware
class RefreshToken extends BaseMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     *
     * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
     *
     * @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->parseToken()->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);
    }
}

當過期之后,會返回一個新的token到headers中,前端可以判斷返回的headers中是否有token,有的話存儲,下次需要時候就提交新的token。

設置 Axios 攔截器

我選用的 HTTP 請求套件是 axios。為了達到無痛刷新 token 的效果,我們需要對 axios 定義一個攔截器,用以接收我們刷新的 Token,代碼如下:

app.js

import Vue from 'vue'
import router from './router'
import store from './store'
import iView from 'iview'
import 'iview/dist/styles/iview.css'

Vue.use(iView)

new Vue({
    el: '#app',
    router,
    store,
    created() {
        // 自定義的 axios 響應攔截器
        this.$axios.interceptors.response.use((response) => {
            // 判斷一下響應中是否有 token,如果有就直接使用此 token 替換掉本地的 token。你可以根據你的業務需求自己編寫更新 token 的邏輯
            var token = response.headers.authorization
            if (token) {
                // 如果 header 中存在 token,那么觸發 refreshToken 方法,替換本地的 token
                this.$store.dispatch('refreshToken', token)
            }
            return response
        }, (error) => {
            switch (error.response.status) {

                // 如果響應中的 http code 為 401,那么則此用戶可能 token 失效了之類的,我會觸發 logout 方法,清除本地的數據並將用戶重定向至登錄頁面
                case 401:
                    return this.$store.dispatch('logout')
                    break
                // 如果響應中的 http code 為 400,那么就彈出一條錯誤提示給用戶
                case 400:
                    return this.$Message.error(error.response.data.error)
                    break
            }
            return Promise.reject(error)
        })
    }
})

Vuex 內的代碼如下:

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        name: null,
        avatar: null,
        mobile: null,
        token: null,
        remark: null,
        auth: false,
    },
    mutations: {
        // 用戶登錄成功,存儲 token 並設置 header 頭
        logined(state, token) {
            state.auth = true
            state.token = token
            localStorage.token = token
        },
        // 用戶刷新 token 成功,使用新的 token 替換掉本地的token
        refreshToken(state, token) {
            state.token = token
            localStorage.token = token
            axios.defaults.headers.common['Authorization'] = state.token
        },
        // 登錄成功后拉取用戶的信息存儲到本地
        profile(state, data) {
            state.name = data.name
            state.mobile = data.mobile
            state.avatar = data.avatar
            state.remark = data.remark
        },
        // 用戶登出,清除本地數據
        logout(state){
            state.name = null
            state.mobile = null
            state.avatar = null
            state.remark = null
            state.auth = false
            state.token = null

            localStorage.removeItem('token')
        }
    },
    actions: {
         // 登錄成功后保存用戶信息
        logined({dispatch,commit}, token) {
            return new Promise(function (resolve, reject) {
                commit('logined', token)
                axios.defaults.headers.common['Authorization'] = token
                dispatch('profile').then(() => {
                    resolve()
                }).catch(() => {
                    reject()
                })
            })
        },
        // 登錄成功后使用 token 拉取用戶的信息
        profile({commit}) {
            return new Promise(function (resolve, reject) {
                axios.get('profile', {}).then(respond => {
                    if (respond.status == 200) {
                        commit('profile', respond.data)
                        resolve()
                    } else {
                        reject()
                    }
                })
            })
        },
        // 用戶登出,清除本地數據並重定向至登錄頁面
        logout({commit}) {
            return new Promise(function (resolve, reject) {
                commit('logout')
                axios.post('auth/logout', {}).then(respond => {
                    Vue.$router.push({name:'login'})
                })
            })
        },
        // 將刷新的 token 保存至本地
        refreshToken({commit},token) {
            return new Promise(function (resolve, reject) {
                commit('refreshToken', token)
            })
        },
    }
})

更新異常處理的 Handler

由於我們構建的是 api 服務,所以我們需要更新一下 app/Exceptions/Handler.php 中的 render

方法,自定義處理一些異常。

Handler.php

<?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
{
    ...

    /**
     * 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);
    }
}

更新完此方法后,我們上面自定義的中間件里拋出的異常和我們下面參數驗證錯誤拋出的異常都會被轉為指定的格式拋出。

使用

現在,我們可以在我們的 routes/api.php 路由文件中新增幾條路由來測試一下了:

api.php新增路由 測試token刷新

將token過期時間設置為1 分鍾,然后使用拿到的token 去驗證一下刷新 token 。

路由文件

.
.
.

//需要 token 驗證的接口
    Route::group(['middleware'=>['jwt.auth']],function (){

        Route::any('jiekou','TestController@jiekou');

    });


    //刷新token的接口 前端每次請求都需要帶上這個接口
    Route::middleware('refresh.token')->group(function (){

        Route::any('profile','UserController@profile');
    });

在 app/kernel.php 文件下添加路由中間件:

protected $routeMiddleware = [
        .
        .
        .
        'refresh.token'=> \App\Http\Middleware\RefreshToken::class,
    ];


jwt.auth "Token not provided","Token has expired"

token 的創建以及解析

使用 token

有兩種使用方法:

  • 加到 url 中:?token=你的token
  • 加到 header 中,建議用這種,因為在 https 情況下更安全:Authorization:Bearer 你的token

token 的創建

前面的 AuthController.php 中有兩行展現了這一種 token 的創建方法,即用用戶所給的賬號和密碼進行嘗試,密碼正確則用對應的 User 信息返回一個 token 。

但 token 的創建方法不止這一種,接下來介紹 token 的三種創建方法:

  • 基於賬密參數
  • 基於 users 模型返回的實例
  • 基於 users 模型中的用戶主鍵 id

a) 基於賬密參數

// 使用輔助函數
$credentials = request(['email', 'password']); 
$token = auth()->attempt($credentials)

// 使用 Facade
$credentials = $request->only('email', 'password');
$token = JWTAuth::attempt($credentials);

b) 基於 users 模型返回的實例

// 使用輔助函數
$user = User::first();
$token = auth()->login($user);

// 使用 Facade
$user = User::first();
$token = JWTAuth::fromUser($user);

c) 基於 users 模型中的主鍵 id

// 使用輔助函數
$token = auth()->tokenById(1);

// 使用 Facade
源碼中沒找到

token 的解析

a) 解析 token 到對象

只有 Facade 需要這樣。

// 把請求發送過來的直接解析到對象
JWTAuth::parseToken();

b) 獲取 token 中的 user 信息

// 輔助函數
$user = auth()->user();

// Facade
$user = JWTAuth::parseToken()->authenticate();

c) 獲取 token

如果 token 被設置則會返回,否則會嘗試使用方法從請求中解析 token ,如果 token 未被設置或不能解析最終返回 false。

// 輔助函數
$token = auth()->getToken();

// Facade
$token = JWTAuth::parseToken()->getToken();

d) 如果是前端

直接 base64 解碼 token 的前兩段即可以知道所需的信息。因為載荷是用 Base64Url 編碼,所以相當於明文,因此絕對不能放密碼等敏感信息。

iss(Issuser):代表這個JWT的簽發主體;
sub(Subject):代表這個JWT的主體,即它的所有人;
aud(Audience):代表這個JWT的接收對象;
exp(Expiration time):是一個時間戳,代表這個JWT的過期時間;
nbf(Not Before):是一個時間戳,代表這個JWT生效的開始時間,意味着在這個時間之前驗證JWT是會失敗的;
iat(Issued at):是一個時間戳,代表這個JWT的簽發時間;
jti(JWT ID):是JWT的唯一標識

php base64_decode()第二段會解析出過期時間戳。

token 的三個時間

一個 token 一般來說有三個時間屬性,其配置都在 config/jwt.php 內。

有效時間

有效時間指的的是你獲得 token 后,在多少時間內可以憑這個 token 去獲取內容,逾時無效。

// 單位:分鍾
'ttl' => env('JWT_TTL', 60)

刷新時間

刷新時間指的是在這個時間內可以憑舊 token 換取一個新 token。例如 token 有效時間為 60 分鍾,刷新時間為 20160 分鍾,在 60 分鍾內可以通過這個 token 獲取新 token,但是超過 60 分鍾是不可以的,然后你可以一直循環獲取,直到總時間超過 20160 分鍾,不能再獲取。

// 單位:分鍾
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160)

寬限時間

寬限時間是為了解決並發請求的問題,假如寬限時間為 0s ,那么在新舊 token 交接的時候,並發請求就會出錯,所以需要設定一個寬限時間,在寬限時間內,舊 token 仍然能夠正常使用。

// 寬限時間需要開啟黑名單(默認是開啟的),黑名單保證過期token不可再用,最好打開
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true)

// 設定寬限時間,單位:秒
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 60)

JWT 的 兩個 Facade

JWTAuth

JWTAuth::parseToken()->方法() 一般都可以換成 auth()->方法()。

token 生成

attempt

根據 user 賬密新建一個 token。

$credentials = $request->only('email', 'password');
$token = JWTAuth::attempt($credentials);

fromUser or fromSubject

根據 user 對象生成一個 token。后者是前者別名。

$user = User::find(1);
$token = JWTAuth::fromUser($user);

token 控制#

refresh

更新 token。

$newToken = JWTAuth::parseToken()->refresh();

invalidate

讓一個 token 無效。

JWTAuth::parseToken()->invalidate();

check

檢驗 token 的有效性。

if(JWTAuth::parseToken()->check()) {
    dd("token是有效的");
}

token 解析

authenticate or toUser or user

這三個效果是一樣的,toUser 是 authenticate 的別名,而 user 比前兩者少一個 user id 的校驗,但並沒有什么影響。

$user = JWTAuth::parseToken()->toUser();

parseToken

從 request 中解析 token 到對象中,以便進行下一步操作。

JWTAuth::parseToken();

getToken

從 request 中獲取 token。

$token = JWTAuth::getToken();  // 這個不用 parseToken ,因為方法內部會自動執行一次

JWTGuard

這個 Facade 主要進行載荷的管理,返回一個載荷對象,然后可以通過 JWTAuth 來對其生成一個 token。

// 載荷的高度自定義
$payload = JWTFactory::sub(123)->aud('foo')->foo(['bar' => 'baz'])->make();
$token = JWTAuth::encode($payload);
$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
$payload = JWTFactory::make($customClaims);
$token = JWTAuth::encode($payload);

其他一些用法

這里用 auth 的寫法,因為 Laravel 有多個 guard,默認 guard 也不是 api ,所以需要寫成 auth('api') 否則,auth() 即可。

設置載荷

$token = auth('api')->claims(['foo' => 'bar'])->attempt($credentials);

驗證賬密是否正確

$boolean = auth('api')->validate($credentials);

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小時,您也可以將其設置為空,以產生永不過期的標記,
    |  注意:Notice: If you set this to null you should remove 'exp' element from 'required_claims' list.
    */

    '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,

    ],

];

跨域

詳細配置過程,請查看官方文檔。

在配置文件 cors.php 中,設置 exposedHeaders,不然無法跨域獲取到 header 中的 Authorization 值來刷新前端令牌。

'exposedHeaders' => ['Authorization'],

刷新時間

刷新時間指的是在這個時間內可以憑舊 token 換取一個新 token。例如 token 有效時間為 60 分鍾,刷新時間為 20160 分鍾。代表你的 token 在 60 分鍾內可以正常使用,超過 60 分鍾則過期無法使用。但在 20160 分鍾的期限內你可以在任意時刻憑舊 token 換取新 token。
刷新token后舊的token不能用了。

其實刷新 Token 就是將舊 Token 放入黑名單,然后生成一個新的 Token,
tymon/jwt-auth/src/Manager.php
https://learnku.com/articles/12679/jwt-auth-blacklist-function
同一時間只允許登錄唯一一台設備。例如設備 A 中用戶如果已經登錄,那么使用設備 B 登錄同一賬戶,設備 A 就無法繼續使用了。
我們可以給用戶表新增一個字段,或者單獨使用一張表,總之是需要先將用戶的 Token 存下來,那么下次用戶再次登錄時,調用如下代碼將舊 Token 加入黑名單:

\JWTAuth::setToken($oldToken)->invalidate();
然后將新的 Token 保存下來,最后將 Token 返回。
這里會有一個問題,如果這個用戶長時間沒登錄過,Token 已無效。再用這個 oldToken 去加入黑名單會報錯。

可以這樣寫
/* @var \Tymon\JWTAuth\JWTGuard $apiGuard */
$apiGuard = auth('api');
$apiGuard->setToken($oldToken);

 // 檢查舊 Token 是否有效
 if ($apiGuard->check()) {
    // 加入黑名單
    $apiGuard->invalidate();
 }

根據一個 token 串獲取用戶是否已認證,可以使用:
$ok = JWTAuth::setToken('your_token_string')->check(); // true or false


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM