~/blog/laravel-11-jwt-refresh-token-security-guide.md
網站安全與防護 · 2026 / 01 / 11

把 JWT 守到滴水不漏:Laravel 11 Refresh Token 輪替與資安防護實戰

Eric — 浪花科技創辦人 / AI 架構師
Eric
浪花科技創辦人 · AI 架構師
把 JWT 守到滴水不漏:Laravel 11 Refresh Token 輪替與資安防護實戰
目錄 table-of-contents.md

最近在幫客戶做 Code Review 的時候,看到一個很有趣(或者說驚悚)的現象。很多剛接觸 Laravel API 開發的朋友,對於 JWT(JSON Web Token)的理解還停留在「安裝套件、拿到 Token、放在 Header 裡發送」就結束了。

甚至我看過有專案為了「方便」,直接把 Access Token 的過期時間設成 一年。這種做法跟把家裡鑰匙放在大門口地墊下一樣,只要鑰匙(Token)被偷,駭客就有整整一年的時間在你家進出自如。這不是 API 開發,這是在開玩笑。

隨著 Laravel 11 的發布,專案結構有了不小的變動(尤其是 Middleware 的註冊方式),今天我們就來談談在 2025 年的現在,如何正確、安全地實作 Laravel API 開發與 JWT 認證,重點會放在「Refresh Token 輪替機制」與 Laravel 11 的整合實戰。

為什麼 Access Token 不能設永久?

在深入程式碼之前,我們先來聊聊架構。JWT 的最大優勢是無狀態(Stateless),伺服器不需要查資料庫就能驗證使用者身份。但這也是它的雙面刃:一旦 Token 發出去,在過期之前,伺服器很難「收回」它(除非你用了黑名單機制,但這又變回有狀態了)。

為了平衡安全與體驗,標準的 JWT 認證架構應該包含兩種 Token:

  • Access Token(存取權杖): 壽命短(例如 15-60 分鐘)。用來請求 API 資源。就算被偷,駭客也只能爽一下下。
  • Refresh Token(刷新權杖): 壽命長(例如 2 週)。用來在 Access Token 過期後,換取新的 Access Token。通常儲存在更安全的地方(如 HttpOnly Cookie),並且只能使用一次(Refresh Rotation)。

Laravel 11 環境準備與套件選擇

在 Laravel 社群中,原本主流的 tymon/jwt-auth 更新頻率較低,目前業界多推薦使用其分支 php-open-source-saver/jwt-auth,它對新版 PHP 和 Laravel 的支援度更好。

1. 安裝套件

composer require php-open-source-saver/jwt-auth

2. 發布設定檔

php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"

3. 產生 JWT Secret

php artisan jwt:secret

這一步很重要,它會更新你的 .env 檔。記得,這個 Secret 千萬不能進版控(Git),否則你的簽章就毫無安全性可言。

Laravel 11 的 Middleware 變革

這是 Eric 覺得 Laravel 11 改動最有感的地方。以前我們習慣去 app/Http/Kernel.php 註冊 Middleware,但現在 Kernel 檔案已經被移除了!所有的 Middleware 設定都移到了 bootstrap/app.php

如果你的 JWT 套件需要註冊 Alias 或全域 Middleware,要在這裡設定:

// bootstrap/app.php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        // Laravel 11 註冊別名的方式
        $middleware->alias([
            'auth.jwt' => \PHPOpenSourceSaver\JWTAuth\Http\Middleware\Authenticate::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

實作 Refresh Token 輪替機制 (Token Rotation)

這是本篇文章的精華。我們不只要讓使用者能登入,還要讓他們能「無感續期」。當前端發現 API 回傳 401 Unauthorized (Token Expired) 時,應該自動拿 Refresh Token 去換新的 Access Token。

AuthController 的設計

這是一個標準的 Laravel Controller 範例,我們重點看 refresh 方法:

// app/Http/Controllers/AuthController.php

namespace App\Http\Controllers;

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

class AuthController extends Controller
{
    public function __construct()
    {
        // 除了 login,其他都需要驗證
        // 注意:refresh 路由通常也會需要驗證舊的 token 格式,或者允許過期的 token 通過 middleware
    }

    /**
     * 刷新 Token
     */
    public function refresh()
    {
        try {
            // 這裡會將舊的 Token 加入黑名單,並回傳新的 Token
            $newToken = Auth::refresh();
            
            return $this->respondWithToken($newToken);
        } catch (\Exception $e) {
            // 如果 Refresh Token 也過期了,或是已被加入黑名單
            return response()->json(['error' => 'Refresh token expired or invalid'], 401);
        }
    }

    /**
     * 統一的回傳格式
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => Auth::factory()->getTTL() * 60 // 單位是秒
        ]);
    }
}

設定 config/jwt.php 的關鍵參數

為了安全,我們必須啟用黑名單機制,確保舊的 Token 在刷新後立即失效。打開 config/jwt.php 檢查以下設定:

  • 'blacklist_enabled' => true,:一定要開。
  • 'ttl' => 60,:Access Token 存活時間,建議短一點(分鐘)。
  • 'refresh_ttl' => 20160,:Refresh Token 存活時間(分鐘),通常設為兩週。
  • 'blacklist_grace_period' => 30,這個超重要!

為什麼要有 Grace Period (寬限期)?這是我在處理高並發 API 時血淋淋的教訓。當前端同時發出 5 個 API 請求,剛好 Token 過期了。第一個請求觸發了 Refresh,舊 Token 被作廢。這時候第 2 到第 5 個請求如果帶著舊 Token 到達伺服器,會因為「Token 已在黑名單」而被拒絕,導致前端炸裂。

設定 30 秒的寬限期,允許舊 Token 在刷新後的 30 秒內依然可以通過驗證(或是被視為有效),能完美解決並發請求的問題。

前端的處理邏輯(給全端工程師的建議)

雖然我們是寫後端,但了解前端如何接球很重要。前端應該使用 Axios Interceptor

  1. 發送請求。
  2. 收到 401 錯誤。
  3. 檢查錯誤碼是否為 Token Expired
  4. 若是,暫停所有請求,發送 /api/refresh 請求。
  5. 拿到新 Token,更新 LocalStorage/Cookie。
  6. 重試原本失敗的請求。

將 Token 存在 LocalStorage 是最簡單的,但也最容易受到 XSS(跨站腳本攻擊)的威脅。如果你的專案對安全性要求極高,Eric 建議將 Token 寫入 HttpOnly Cookie

在 Laravel 中,你可以建立一個 Middleware 將 Response 加上 Cookie:

$cookie = cookie('jwt_token', $token, 60, null, null, true, true); // HttpOnly = true
return response()->json([...])->withCookie($cookie);

這樣 JavaScript 就讀不到 Token,大大降低了 Token 被竊取的風險。

結論

Laravel API 開發與 JWT 認證絕對不是「會動就好」。在 Laravel 11 的新架構下,我們更要注意 Middleware 的配置位置,以及 Token 的生命週期管理。透過合理的 TTL 設定、黑名單寬限期,以及正確的前端攔截機制,我們才能打造出一個既安全又順暢的 API 系統。

別再當那個把 Access Token 設為一年的工程師了,好嗎?

延伸閱讀

如果你的企業系統正面臨 API 效能瓶頸,或是對於資安架構感到不放心,歡迎隨時聯繫浪花科技。我們專注於打造高流量、高可用的 Laravel 與 WordPress 解決方案。

聯繫我們,獲取專業技術諮詢
// FAQ

常見問題

JWT 的 Access Token 為什麼不能設成永久或太長?
因為 JWT 是無狀態的,一旦發出去在過期前伺服器很難收回。若 Access Token 壽命過長甚至設成一年,被竊取後攻擊者就能長時間冒用身分。標準做法是讓 Access Token 壽命短(如 15 到 60 分鐘)用來請求資源,再搭配壽命較長的 Refresh Token 換發新權杖。
Laravel 11 的 Middleware 註冊方式有什麼變動?
Laravel 11 移除了 app/Http/Kernel.php,所有 Middleware 設定都改到 bootstrap/app.php。要註冊 JWT 套件的別名或全域 Middleware,需在 withMiddleware 的閉包中使用 $middleware->alias() 來設定,例如將 auth.jwt 對應到套件的 Authenticate Middleware。
在 Laravel 做 JWT 認證該選哪個套件?
原本主流的 tymon/jwt-auth 更新頻率較低,業界目前多推薦使用其分支 php-open-source-saver/jwt-auth,它對新版 PHP 與 Laravel 的支援度更好。安裝後需發布設定檔並執行 php artisan jwt:secret 產生簽章用的 Secret,且該 Secret 絕不能進版控。
JWT 黑名單的寬限期(blacklist_grace_period)是用來解決什麼問題?
它用來解決高並發下的 Token 刷新衝突。當前端同時發出多個請求且 Token 剛好過期,第一個請求觸發刷新後舊 Token 立即作廢,其餘帶舊 Token 的請求就會因「已在黑名單」被拒。設定例如 30 秒的寬限期,允許舊 Token 在刷新後短時間內仍有效,可避免並發請求大量失敗。
~/roamer-tech/newsletter // FREE
// newsletter

訂閱免費電子報

把 AI 自動化、企業系統設計與 WordPress / Laravel 開發的真實案例和可直接照做的技巧,整理成電子報寄給你。只寄精選內容、不灌垃圾信,一鍵就能退訂。

$
// final.exec()

準備好讓你的網站開始為你工作了嗎?