打造 Laravel 11 銅牆鐵壁:JWT 無狀態驗證實戰
您的 API 登入機制還在依賴傳統且低效的 Session 與 Cookie 嗎?資深工程師 Eric 警告,有狀態驗證是跨域地獄和擴展性的殺手!本文將帶您進入 2025 年業界標準:JWT(JSON Web Token)。我們將以最新的 Laravel 11 為基礎,透過實戰步驟部署 `jwt-auth`,從 Model 改造到 Auth Guard 設定,建立一個高效、易於擴展的無狀態驗證堡壘。別再讓您的後門洞開!立即行動,強化您的 API 防線。如果想確保系統具備最高的資安標準與 Refresh Token 輪替機制,立即聯繫浪花科技,為您的專案打造堅不可摧的後端架構!
API 像公共廁所隨便進?Laravel 11 JWT 終極實戰:打造駭客絕望的無狀態驗證堡壘
大家好,我是浪花科技的資深工程師 Eric。今天我們不聊那些虛無縹緲的理論,直接來談談你的 API 後門。
我常在 Code Review 時看到新手工程師(有時候甚至是資深老鳥)在開發前後端分離的系統時,還在試圖用 Session 和 Cookie 來處理 API 的登入狀態。兄弟,醒醒吧!當你的前端是 Vue、React,甚至是 iOS 或 Android App 時,Session 那套依賴瀏覽器的機制只會讓你陷入 CORS (跨來源資源共用) 的無限地獄,而且伺服器還得記住每個人的 Session ID,流量一大,Redis 或 Memcached 直接爆給你看。
在 2025 年的現在,JWT (JSON Web Token) 幾乎是 RESTful API 驗證的業界標準。今天這篇文章,Eric 要帶大家在最新的 Laravel 11 環境下,從零打造一個銅牆鐵壁般的「無狀態 (Stateless)」驗證系統。我們不只要「能動」,還要「安全」。
為什麼你的 API 需要 JWT?(Session 到底錯在哪?)
簡單來說,傳統的 Session 是「有狀態 (Stateful)」的。伺服器必須在記憶體或資料庫中記住「誰登入了」。
- 擴展性差: 如果你有 10 台伺服器做負載平衡,你還得搞 Session 共享,架構瞬間變複雜。
- 跨域問題: 前端網域跟後端不同,Cookie 傳遞困難重重。
- 行動裝置不友善: 原生 App 處理 Cookie 遠比處理 Header 裡的 Token 麻煩。
而 JWT 是「無狀態 (Stateless)」的。伺服器不需要記住任何東西。Token 本身就包含了使用者的身份資訊(Payload)和一個防止被篡改的簽名(Signature)。前端把 Token 放在 Header 裡丟過來,Laravel 只要拿密鑰驗證簽名是對的,就放行。這才是現代 API 該有的樣子。
實戰開始:Laravel 11 + JWT Auth
在 Laravel 生態系中,雖然官方有 Passport 和 Sanctum,但如果你想要最純粹、完全掌控的 JWT 實作,php-open-source-saver/jwt-auth(原 tymon/jwt-auth 的維護分支)依然是首選。
步驟 1:安裝套件
打開你的終端機,別手軟,指令敲下去:
composer require php-open-source-saver/jwt-auth
步驟 2:發布設定檔與生成密鑰
安裝完後,我們需要把設定檔拉出來,並且生成一個用來加密的 Secret Key。這個 Key 就是你的命根子,絕對不能外洩。
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret
執行後,你會在 .env 檔裡看到一行 JWT_SECRET=xxxxx。如果這一行洩漏了,駭客就能偽造任何人的 Token 登入你的系統,切記保護好它。
步驟 3:改造 User Model
接下來,我們要讓 Laravel 的 User 模型支援 JWT。打開 app/Models/User.php,我們需要實作 JWTSubject 介面。
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject; // 記得引入這個
class User extends Authenticatable implements JWTSubject
{
// ... 其他原有的程式碼 ...
/**
* 取得會儲存在 JWT Subject Claim 的識別值 (通常是 user id)
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* 返回一個鍵值陣列,包含要加入到 JWT payload 中的自訂聲明
*/
public function getJWTCustomClaims()
{
return []; // 可以在這裡加入 role, email 等非敏感資訊
}
}
Eric 的小囉嗦:在 getJWTCustomClaims 裡,千萬不要放密碼、身分證字號這種敏感個資!因為 JWT 的 Payload 只是 Base64 編碼,是可以被輕易解碼看到的。這裡頂多放個 role_id 或 username 就好。
步驟 4:設定 Auth Guard
打開 config/auth.php,我們要告訴 Laravel,針對 API 的驗證,我們要改用 JWT 驅動。
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt', // 這裡改成 jwt
'provider' => 'users',
],
],
步驟 5:打造 AuthController
重頭戲來了,我們來寫一個處理登入、登出、取得個人資料的控制器。在終端機輸入 php artisan make:controller AuthController。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
class AuthController extends Controller
{
/**
* 建構子,設定 middleware
*/
public function __construct()
{
// 除了 login 以外,其他方法都需要驗證
$this->middleware('auth:api', ['except' => ['login']]);
}
/**
* 登入並取得 Token
*/
public function login()
{
$credentials = request(['email', 'password']);
if (! $token = auth('api')->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
/**
* 取得當前登入使用者的資料
*/
public function me()
{
return response()->json(auth('api')->user());
}
/**
* 登出 (讓 Token 失效)
*/
public function logout()
{
auth('api')->logout();
return response()->json(['message' => 'Successfully logged out']);
}
/**
* 刷新 Token (換取新 Token)
*/
public function refresh()
{
return $this->respondWithToken(auth('api')->refresh());
}
/**
* 統一回傳 Token 的格式
*/
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60 // 預設單位是分鐘,換算成秒
]);
}
}
步驟 6:設定 API 路由
最後一步,到 routes/api.php 把路徑接通。
use App\Http\Controllers\AuthController;
Route::group(['middleware' => 'api', 'prefix' => 'auth'], function ($router) {
Route::post('login', [AuthController::class, 'login']);
Route::post('logout', [AuthController::class, 'logout']);
Route::post('refresh', [AuthController::class, 'refresh']);
Route::post('me', [AuthController::class, 'me']);
});
資安加固:Eric 的經驗談
做到上面那樣,你的 API 已經可以運作了,但在資深工程師眼裡,這只是個半成品。以下幾點是你必須注意的:
- HTTPS 是標配: 不要以為用了 Token 就可以不用 SSL。如果沒有 HTTPS,Token 在網路上裸奔,一樣會被攔截竊取。
- Token 時效性 (TTL): 預設 Token 有效期可能是一小時 (60分鐘)。別設太長(例如一年),否則 Token 被盜用造成的風險極大。通常建議 Access Token 設短一點(例如 15-60 分鐘),搭配 Refresh Token 機制來延長使用時間。
- 黑名單機制: JWT 最大的缺點是一旦發出去,在過期前都有效。Laravel 的 JWT 套件有實作「黑名單 (Blacklist)」功能。當使用者登出時,其實是把該 Token 加入黑名單,讓它提早失效。請確保你的 Cache Driver (如 Redis) 有設定好,以保持高效能。
結語
API 的驗證機制是整個系統安全的第一道防線,使用 Laravel 11 搭配 JWT,可以讓你輕鬆構建出高效、安全且易於擴展的後端服務。別再留戀 Session 了,勇敢地擁抱無狀態架構吧!
如果在實作過程中遇到任何怪問題,或是對於 Refresh Token 的輪替機制感到頭痛,歡迎隨時來找我們。
延伸閱讀
- 你的 API 登入機制安全嗎?Laravel 11 JWT 進階實戰:Refresh Token 輪替與資安防護全攻略
- 你的 API 像公共廁所隨便進?Laravel 11 驗證 (Validation) 與 Middleware 客製化終極實戰
- 告別資料打架!Laravel 整合 HubSpot API v3 實戰:雙向同步、Rate Limit 與關聯資料的終極解法
你的 Laravel API 安全性足夠嗎?或是需要建置高併發的後端架構?
常見問題 (FAQ)
Q1: 為什麼登出後舊的 Token 還是可以拿來打 API?
這通常是因為 JWT 的黑名單 (Blacklist) 機制沒有啟用或設定錯誤。JWT 本身是無狀態的,伺服器無法主動讓客戶端手上的 Token 失效,必須依賴伺服器端的 Cache (如 Redis) 來記錄已登出的 Token ID (jti),並在每次請求時比對。請檢查 config/jwt.php 中的 blacklist_enabled 是否為 true。
Q2: Token 應該存在前端的 LocalStorage 還是 Cookie?
這是一個經典爭論。存 LocalStorage 方便但容易受到 XSS 攻擊;存 HttpOnly Cookie 可以防 XSS 但要處理 CSRF 問題。對於 API 服務,通常建議存 LocalStorage 但必須嚴格過濾所有前端輸出來防堵 XSS;如果安全性要求極高,可以考慮 HttpOnly Cookie 搭配 CSRF Token 方案。
Q3: Laravel 11 安裝 JWT 時出現版本衝突怎麼辦?
請確認你安裝的是 php-open-source-saver/jwt-auth 而不是已經停止維護的 tymon/jwt-auth。此外,檢查你的 PHP 版本是否符合套件要求,Laravel 11 通常需要 PHP 8.2 以上。






