這是一篇關於如何在 Laravel
框架中使用 JSON Web Tokens (JWT)
來實現從一個網站(A
網站)到另一個網站(B
網站)無需重新登入功能的指南。這個功能不僅提高了使用者體驗,同時也確保了跨站點的安全性。下面我們來一步一步地看看如何設置吧!
首先,我們得在 A
網站上設置好 JWT
。這個過程分幾個簡單的步驟:
打開你的終端機,運行以下命令來安裝 JWT 套件:
composer require tymon/jwt-auth
接下來,我們需要生成配置文件和密鑰,以便 JWT
能夠正確地加密和解密令牌:
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret
在 auth.php
配置文件中,我們要設定使用 jwt
作為驗證驅動:
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
在你的登入控制器中,添加生成 JWT
的邏輯:
use Tymon\JWTAuth\Facades\JWTAuth;
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
return response()->json(['token' => $token]);
}
接著,我們在 A
網站生成一個包含 JWT
的連結,當用戶點擊這個連結時,他們將被導向到 B
網站並自動登入:
public function getAuthenticatedLink()
{
$token = JWTAuth::fromUser(auth()->user());
$link = 'https://b-website.com/login?token=' . $token;
return response()->json(['link' => $link]);
}
在生成 JWT
時,我們可以加入一些特定的聲明(claims),例如 iat(Issue At,發行時間)、exp(Expiration Time,過期時間)和 nbf(Not Before,不早於時間)。這些聲明幫助我們控制令牌的有效期和使用時機,來避免一些常見的安全問題,比如令牌重放攻擊。
假設你希望令牌在生成後的一小時內有效,並且有一些自定義的資料(比如用戶類型等),你可以這樣做:
public function getAuthenticatedLink()
{
$user = auth()->user();
$currentTime = new \DateTimeImmutable();
$customClaims = [
'customize' => 'customize', // 自定義
'exp' => $currentTime->modify('+1 hour'), // 過期時間
];
$token = JWTAuth::claims($customClaims)->fromUser($user);
$link = 'https://b-website.com/login?token=' . $token;
return redirect($link);
}
現在,在 B 網站,我們需要設置好 JWT 並確保能夠接收來自 A 網站的 JWT,進行驗證和登入用戶:
透過php artisan jwt:secret
生成的 JWT_SECRET
需也放到b網站
use Tymon\JWTAuth\Facades\JWTAuth;
public function loginWithToken(Request $request)
{
$token = $request->get('token');
if (!$token) {
\Log::error('Token not provided');
return response()->json(['error' => 'token_not_provided'], 400);
}
try {
$payload = JWTAuth::parseToken($token)->getPayload();
$user = JWTAuth::parseToken($token)->authenticate();
if (!$user) {
return response()->json(['error' => 'user_not_found'], 404);
}
} catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
\Log::error('Token expired', ['token' => $token, 'message' => $e->getMessage()]);
return response()->json(['error' => 'token_expired'], 401);
} catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
\Log::error('Token invalid', ['token' => $token, 'message' => $e->getMessage()]);
return response()->json(['error' => 'token_invalid'], 401);
} catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
\Log::error('Token absent', ['token' => $token, 'message' => $e->getMessage()]);
return response()->json(['error' => 'token_absent'], 401);
}
// 登入用戶
Auth::login($user);
// 取得自定義的 customize
$customize = $payload->get('customize');
return redirect('/home'); // 重定向到登入後的頁面
}
當你需要產生一個不包括使用者身分(如使用者 ID 或使用者名稱)的 JWT
時,可以使用 JWTFactory
類別來建立一個包含自訂聲明的 token
。這種方式不依賴特定的使用者模型。
以下是一個簡單的例子,展示如何產生這種 JWT
:
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Facades\JWTFactory;
public function index()
{
$customClaims = [
'iss' => 'your-issuer-identifier', // Issuer
'sub' => 'subject-or-user-id', // Subject, generally user identifier
'aud' => 'your-audience', // Audience
'customize' => 'customize',
'action' => 'menu',
];
$payload = JWTFactory::customClaims($customClaims)->make();
$token = JWTAuth::encode($payload)->get();
$link = 'https://b-website.com/login-withtoken/?token=' . $token;
return redirect($link);
}
在上述代碼中,我們首先定義了一組自定義聲明,這些聲明描述了 JWT
的一些基本特性,如發行者、主題和觀眾。然後,我們通過 JWTFactory
創建了一個 payload
,這個 payload
包含了我們的自定義聲明。接著,使用 JWTAuth::encode()
方法將這個 payload
編碼成一個 JWT
。最後,我們將這個 JWT
添加到一個 URL
中,並將用戶重定向到該 URL
。
此方法允許你靈活地生成 JWT
,適用於不需要綁定特定用戶身份的場景,從而可以在多種應用環境中靈活使用。
JWT
只包含自定義的宣告,所以在驗證用戶身份時可能需要額外的安全措施。JWT
可以更方便地關聯用戶數據。在使用 Laravel
的 tymon/jwt-auth
套件時,通常我們只需要一組密鑰(JWT_SECRET)來簽名和驗證令牌,這對大多數應用來說已經夠用了。但是,如果你的應用情境比較特殊,比如說在一個多租戶系統中,或者需要和多個不同的服務進行接口對接,你可能會需要用到多組密鑰。雖然 tymon/jwt-auth
本身不直接支持多組密鑰,但我們可以自己動手搞定。
首先,在你的 .env
檔案裡面加入多組密鑰。就像這樣設定:
JWT_SECRET_DEFAULT=your_default_secret_key
JWT_SECRET_ALTERNATE=your_alternate_secret_key
接著,我們要創建一個中間件,這個中間件會根據請求的某些特定條件(比如說一個特定的標頭或者是URL路徑),來選擇使用哪一組密鑰。這裡有個例子展示如何在中間件中根據請求標頭來切換密鑰:
use Tymon\JWTAuth\JWTAuth;
use Closure;
class CustomJWTMiddleware
{
protected $jwtAuth;
public function __construct(JWTAuth $jwtAuth)
{
$this->jwtAuth = $jwtAuth;
}
public function handle($request, Closure $next, $guard = null)
{
// 讀取請求頭中的 'X-Key-Type'
$keyType = $request->header('X-Key-Type', 'default');
// 根據 'X-Key-Type' 決定使用哪個密鑰
$secret = config('jwt.secrets.' . $keyType, config('jwt.secret'));
// 更新配置和 JWTAuth 實例中的密鑰
config(['jwt.secret' => $secret]);
$this->jwtAuth->setSecret($secret);
return $next($request);
}
}
最後,把這個中間件註冊到你的路由或者是全局中間件中。這樣,每次的請求都會先經過這個中間件,確保使用正確的密鑰。
這樣一來,你就能根據不同的需要,動態切換使用哪一組 JWT
密鑰了。
當涉及到使用 JWT
進行跨網站認證時,伺服器之間可能存在的時間差異是一個需要特別注意的問題。這種時間差異可能導致令牌驗證失敗,比如在一個伺服器上剛剛生成的令牌,在另一個時間設定不同的伺服器上可能被認為還沒到有效期或已經過期。以下是一些解決這個問題的策略:
最直接的解決方法是確保所有伺服器的系統時間都同步。這通常可以通過使用 NTP(Network Time Protocol)
服務來實現,它會確保所有伺服器的時間與全球標準時間保持一致。
date
sudo apt-get install ntp
sudo service ntp restart
在生成和驗證 JWT
時,可以人為地設定一個時間容錯範圍。例如,當設定令牌的 nbf(Not Before)
和 exp(Expiration Time)
時,可以考慮在這些時間前後加上一定的緩衝期。這樣即使伺服器之間存在小幅度的時間偏差,也不會影響令牌的有效性。
以下是具體如何在 Laravel
中應用這一策略的示例:
public function getAuthenticatedLink()
{
$user = auth()->user();
$currentTime = new \DateTimeImmutable();
// 自定義聲明,增加時間容錯
$customClaims = [
'custmer' => 'custmer', // 自定義資料
'iat' => $currentTime->modify('-1 minute'), // 發證時間提前1分鐘,增加容錯
'exp' => $currentTime->modify('+1 hour 5 minutes'), // 有效期稍長,增加容錯
'nbf' => $currentTime->modify('-1 minute') // 不早於時間提前1分鐘,增加容錯
];
$token = JWTAuth::claims($customClaims)->fromUser($user);
$link = 'https://b-website.com/login?token=' . $token;
return response()->json(['link' => $link]);
}
在這個例子中,我們將 iat
和 nbf
設置為當前時間前1分鐘,而將 exp
擴展到1小時5分鐘後。這樣做可以提供足夠的時間容錯,以適應可能存在的時間差異。
如果在應用層面進行時間校驗還不足以解決問題,你可以考慮實施中間件來處理時間相關的驗證邏輯。這個中間件可以在每次令牌解析前,先校正或確認時間的一致性,再進行常規的 JWT
驗證。
綜合這些策略,可以有效減少由於伺服器時間不一致導致的認證問題,從而提高系統的穩定性和用戶體驗。希望這些建議能幫助你更好地管理和解決跨服務的時間同步問題!
希望這能幫助你更好地理解和實現 JWT
在實際開發中的應用。記得,保持 JWT
的安全是很重要的,包括適當的令牌時效和安全的存儲方式。祝你開發愉快!