繼上一篇 使用JWT 製作多登入系統 API ,前端經過 API 登入後會得到一個 Token,但避免使用者忘記登出導致 Token 被利用,一般都會將該 Token 設定一個短時效的過期時間,可是 Token 時間設定太短,又容易造成使用者必須頻繁登入來重新取得新的 Token,所以必須要讓使用 API 的使用者能夠自動去更新至換新的 Token。

建立一個 Middleware

php artisan make:middleware JwtRefreshToken

修改 app\Http\Kernel.php

protected $routeMiddleware = [
     ....
     //檢查 JWT Token 及 自動更新 Token
     'refresh.token' => \App\Http\Middleware\JwtRefreshToken::class,
 ];

編輯 JwtRefreshToken

<?php

namespace App\Http\Middleware;

use Closure;
use JWTAuth;
use Exception;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException as TokenInvalidException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException as TokenExpiredException;

class JwtRefreshToken extends BaseMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next){
        try {
            //檢查是否有使用者通過驗證
            $user = JWTAuth::parseToken()->authenticate();

            // //Token TTL 到期時間
            // $expireTime = JWTAuth::payload()['exp'];
            // // 如果 Token 到期時間小於5分鐘,重新產生一個新的 Token 並附帶在 header 送出,讓前端重新抓取.
            // if (($expireTime - time()) < 5*60 && ($expireTime - time()) > 0) {
            //     $refreshed = JWTAuth::refresh(JWTAuth::getToken());
            //     $user = JWTAuth::setToken($refreshed)->toUser();
            //     $request->headers->set('Authorization', 'Bearer '.$refreshed);

            //     // 輸出到BODY測試
            //     return response()->json([
            //         'code' => 200,
            //         'user' => $user,
            //         'access_token' => $refreshed,
            //         'token_type' => 'bearer',
            //         'expires_in' => JWTAuth::factory()->getTTL(),
            //     ]);
            // }

        } catch (Exception $e) {
            //檢查 Token 是否無效
            if ($e instanceof TokenInvalidException){
                $status     = 401;
                $message    = 'This token is invalid. Please Login';
                return response()->json(compact('status','message'),401);
            //檢查 Token 是否到期
            }else if ($e instanceof TokenExpiredException){
                try
                {
                    // 如果 Token 到期,重新產生一個新的 Token 並附帶在 header 送出,讓前端重新抓取.
                    $refreshed = JWTAuth::refresh(JWTAuth::getToken());
                    $user = JWTAuth::setToken($refreshed)->toUser();
                    $request->headers->set('Authorization','Bearer '.$refreshed);

                    // 輸出到BODY測試
                    // return response()->json([
                    //     'code' => 200,
                    //     'user' => $user,
                    //     'access_token' => $refreshed,
                    //     'token_type' => 'bearer',
                    //     'expires_in' => JWTAuth::factory()->getTTL(),
                    // ]);

                }catch (JWTException $e){
                    // Token 無法被 refresh
                    return response()->json([
                        'code'   => 103,
                        'message' => 'Token cannot be refreshed, please Login again'
                    ]);
                }
            }else{
                //找不到 Token.
                $message = 'Authorization Token not found';
                return response()->json(compact('message'), 404);
            }
        }
        return $next($request);
    }
}

修改 API 的 Controller

public function __construct()
 {
     //除了 login 其餘 function 都要經過 api 及 refresh.token 的 middleware 檢查
     $this->middleware(['admapi','refresh.token'], ['except' => ['login']]);
 }

測試

未提供 Token 時讀取資料

登入得到 Token 後可以讀取資料

Token 被更新成另一份新的

Token 被取消將無法被更新

偷改造假 Token 被判定無效

最後修改日期: 2020 年 12 月 11 日