AI 正在初次分析文章並整理建議,請稍候…
API 沒鎖門等於裸奔!2026 Laravel JWT 實戰:從 Refresh Token 輪替到黑名單機制的資安防護網
嗨大家好,我是浪花科技的資深工程師 Eric。又是喝著黑咖啡、盯著 Terminal 發呆的午後。最近幫客戶做系統健檢,打開他們的 API Log 一看,真的差點沒把剛喝進去的咖啡噴在螢幕上。為什麼?因為他們的 API 根本就是「裸奔」狀態,雖然有做 Token 驗證,但那個 Token 的效期居然設了「永久」!沒錯,就是一旦發出去,除非改資料庫,否則拿到 Token 的人(或駭客)可以一輩子自由進出你的系統。
在 2026 年的今天,AI 攻擊機器人滿街跑,這種設定跟把家裡鑰匙掛在大門口有什麼兩樣?
很多開發者聽到 JWT (JSON Web Token) 都覺得:「喔,我不就是裝個套件、產生一串亂碼,然後前端放在 Header 帶過來就好了嗎?」大錯特錯。真正的 JWT 機制,核心在於「無狀態 (Stateless)」的特性以及「Refresh Token (刷新權杖) 的輪替機制」。今天這篇文章,Eric 要帶大家深入 Laravel API 開發的核心,手把手實作一套真正符合 2026 年資安標準的 JWT 認證系統。
為什麼 2026 年我們還在談 JWT?
你可能會問,Laravel 不是有 Sanctum 和 Passport 嗎?為什麼還要自找麻煩用 JWT?
確實,對於簡單的 SPA (Single Page Application) 或同網域的應用,Sanctum 的 Cookie-based 驗證非常方便。但在以下情境,JWT 依然是霸主:
- 跨網域微服務 (Microservices): 當你的認證中心 (Auth Service) 和資源伺服器 (Resource Server) 是分開的,JWT 的自包含 (Self-contained) 特性讓資源伺服器不需要查資料庫就能驗證身份。
- 高併發 Mobile App: 手機端不需要維護複雜的 Cookie 狀態,Token 存取更直觀。
- Server-to-Server 通訊: 機器與機器之間的 API 溝通,JWT 是最通用的標準。
實戰開始:環境準備與套件安裝
假設你已經安裝好了 Laravel 12 或更高版本(沒錯,我們活在 2026 年)。我們依然推薦使用社群維護最穩定的 php-open-source-saver/jwt-auth 套件。
1. 安裝套件
composer require php-open-source-saver/jwt-auth
2. 發布設定檔
這一步很重要,很多人裝完就直接用,結果連密鑰都沒換。請執行:
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"
3. 產生 JWT Secret
這把鑰匙是簽署 Token 的核心,千萬不能外流。這指令會幫你在 .env 檔案中寫入 JWT_SECRET。
php artisan jwt:secret
核心觀念:Access Token 與 Refresh Token 的黃金比例
這是我最常碎念新進工程師的地方。為了安全,我們必須將 Token 分為兩種:
- Access Token (存取權杖): 效期極短(例如 15-60 分鐘)。用來存取 API 資源。因為效期短,就算被劫持,駭客能用的時間也很有限。
- Refresh Token (刷新權杖): 效期較長(例如 2 週)。唯一用途就是用來換取新的 Access Token。
在 config/jwt.php 中,我們需要設定 TTL (Time To Live):
// 單位是分鐘,這裡設定 Access Token 為 60 分鐘
'ttl' => env('JWT_TTL', 60),
// Refresh TTL 設定為 20160 分鐘 (兩週)
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),
程式碼實作:User Model 設定
讓你的 User Model 實作 JWTSubject 介面。這告訴 JWT 套件:「嘿,這個 Model 是要用來產生 Token 的。」
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
// ... 其他程式碼 ...
/**
* 取得會被儲存在 JWT 中的識別字 (通常是 ID)
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* 回傳自訂的 Claims (想要放在 Token 裡的額外資訊)
*/
public function getJWTCustomClaims()
{
return [
'role' => $this->role, // 比如把角色放進去,前端解析 Token 就能知道權限
'iss' => 'RoamerTech_Auth_Server' // 簽發者
];
}
}
Controller 實戰:登入與 Token 簽發
這裡我們要寫一個 AuthController。注意看 respondWithToken 這個方法,這是 Eric 的習慣寫法,統一回傳格式,方便前端工程師接資料。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
class AuthController extends Controller
{
public function __construct()
{
// 除了 login,其他方法都需要驗證
$this->middleware('auth:api', ['except' => ['login']]);
}
/**
* 登入並取得 Token
*/
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
// 嘗試登入,如果失敗回傳 401
if (! $token = Auth::attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
/**
* 取得當前登入的使用者資訊
*/
public function me()
{
return response()->json(Auth::user());
}
/**
* 登出 (讓 Token 失效)
*/
public function logout()
{
Auth::logout();
return response()->json(['message' => 'Successfully logged out']);
}
/**
* 刷新 Token (重點!)
*/
public function refresh()
{
// 這裡會將舊的 Token 加入黑名單,並回傳一個全新的 Token
return $this->respondWithToken(Auth::refresh());
}
/**
* 統一的回傳格式構造器
*/
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => Auth::factory()->getTTL() * 60 // 回傳秒數
]);
}
}
資安關鍵:Token 黑名單 (Blacklist) 機制
這就是「無狀態」驗證最讓人頭痛的地方:如果使用者的手機被偷了,或者我要強制踢某人下線,但我發出去的 Token 還沒過期,怎麼辦?
這時候就需要「黑名單」機制。jwt-auth 套件預設支援這個功能。當使用者呼叫 logout 或 refresh 時,舊的 Token 雖然還沒過期,但會被存入 Cache (通常是 Redis) 標記為「已無效」。
確保你的 .env 設定正確:
JWT_BLACKLIST_ENABLED=true
JWT_BLACKLIST_GRACE_PERIOD=30 # 寬限期 30 秒,避免併發請求時舊 Token 瞬間失效導致錯誤
Eric 建議一定要裝 Redis 來處理黑名單。因為如果是用資料庫或檔案系統,每個 API Request 都要去查表,效能會掉得比股市崩盤還快。
2026 進階防禦:前端如何安全儲存?
寫後端的不能只顧自己爽,也要教前端怎麼存。在 2026 年,將 Token 存在 localStorage 已經被視為高風險行為,因為只要網站有 XSS 漏洞,Token 馬上就會被偷走。
最佳實踐建議:
- 記憶體儲存 (In-Memory): Access Token 只存在 JS 變數中,頁面刷新就消失。
- HttpOnly Cookie 儲存 Refresh Token: 將 Refresh Token 放在
HttpOnly、Secure、SameSite=Strict的 Cookie 中。
這樣做的好處是,JS 讀不到 Cookie (防 XSS),而 Access Token 存在記憶體中,駭客就算攻擊進來也拿不到持久的登入憑證。
常見的 API 認證錯誤 (不要再犯了!)
- 錯誤 1:Token 效期設太長。 不要為了省去 Refresh 的工,把 Access Token 設為一個月。
- 錯誤 2:不驗證簽發者 (iss)。 如果你的公司有多個服務,務必檢查
iss欄位,避免 A 服務的 Token 被拿來存取 B 服務。 - 錯誤 3:將敏感資料塞進 Payload。 JWT 只是 Base64 編碼,不是加密!任何人拿到 Token 都能解碼看到內容。千萬不要把密碼、身分證字號放在裡面。
相關閱讀
要打造一個企業級的 Laravel 系統,單靠 JWT 是不夠的。以下這幾篇 Eric 精選的文章,建議一併服用:
- 141. 你的 API 像公共廁所隨便進?Laravel 11 驗證 (Validation) 與 Middleware 客製化終極實戰 – 驗證是資安的第一道防線。
- 264. 不只是單向拋接!Laravel x HubSpot 進階戰術:打造企業級雙向、容錯、高效率的資料流引擎 – 學會如何處理複雜的資料同步。
- 289. Laravel 效能卡關?Redis 就是你的神兵利器!從快取到隊列,資深工程師帶你榨乾系統效能 – 優化黑名單機制必讀。
常見問題 (FAQ)
Q1: JWT 與 Sanctum 到底該選誰?
如果是 Laravel 作為後端,Vue/React 作為前端的 SPA 架構,且都在同一個主網域下,選 Sanctum (Cookie-based) 最安全簡單。如果是開發純 API 供 Android/iOS 或第三方廠商介接,選 JWT。
Q2: Refresh Token 過期了怎麼辦?
這代表使用者的登入狀態已經太久沒活動,或者超過了強制登出時間。這時候前端應該導向登入頁面,請使用者重新輸入帳號密碼登入。
Q3: 為什麼我的 JWT 黑名單功能沒效?
請檢查 .env 中的 JWT_BLACKLIST_ENABLED 是否為 true,並確認你的 Cache Driver 是否設定正確(建議使用 Redis)。另外,檢查 storage 目錄的權限是否足夠(如果是用 file driver)。
Q4: Token 被偷了怎麼辦?
由於 JWT 是無狀態的,一旦 Access Token 被偷,駭客在效期內都能使用。這就是為什麼 Access Token 效期要短。若發現異常,可以透過更改使用者的 jwt-auth secret (如果邏輯支援) 或在黑名單機制中強制讓該 User ID 的所有 Token 失效。
資安這條路是沒有終點的,JWT 只是基本功。如果你在實作 Laravel API 認證、或是系統架構優化上遇到瓶頸,不知道該怎麼設計最安全、最高效的驗證流程,別自己悶著頭寫 Code 了。
有時候,一個資深工程師的建議,可以幫你省下好幾週的 Debug 時間。
歡迎隨時聯繫浪花科技,讓我們來幫你的系統做個深度健檢!






