單點登入 (SSO) 讓使用者在多個子系統(子域名)間僅需登入一次。本文以 Laravel 為例,實作「中央認證站 (a.example.com) + 子系統 (b.example.com, c.example.com)」架構,採用 Ticket + Cache 驗證機制。
<?php
// AuthController@login
public function login(Request $request) {
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
$user = Auth::user();
$ticket = $this->generateTicket($user);
$redirectUrl = $request->input('source') . '?ticket=' . $ticket;
return redirect($redirectUrl);
}
return back()->withErrors(['登入失敗']);
}
private function generateTicket($user) {
$ticket = md5(time() . config('app.key') . $user->id);
// 快取 2 分鐘,值為使用者資料陣列
Cache::put($ticket, ['uid' => $user->id, 'name' => $user->name], 120);
return $ticket;
}
?>
<?php
// AuthController@checkTicket
public function checkTicket(Request $request) {
$ticket = $request->input('ticket');
$data = Cache::get($ticket);
if ($data) {
// 驗證成功後刪除 Ticket 防重放
Cache::forget($ticket);
return response()->json(['state' => 'SUCCESS', 'result' => $data]);
}
return response()->json(['state' => 'FAIL', 'msg' => 'Ticket 無效或已過期']);
}
?>
<?php
// app/Http/Middleware/CasAuthenticate.php
public function handle($request, Closure $next) {
$ticket = $request->input('ticket');
if ($ticket) {
$response = Http::get('https://a.example.com/auth/check-ticket', ['ticket' => $ticket]);
$result = $response->json();
if ($result['state'] === 'SUCCESS') {
// 登入本地使用者(需同步使用者資料或共用 users 表)
Auth::loginUsingId($result['result']['uid']);
// 移除 ticket 參數避免殘留
return redirect($request->url());
}
}
return $next($request);
}
?>
於 app/Http/Kernel.php 註冊中介層:
protected $middlewareGroups = [
'web' => [
// ...
\App\Http\Middleware\CasAuthenticate::class,
],
];
於 config/session.php 設定:
'domain' => '.example.com', // 所有子域名共享 Session Cookie 'same_site' => 'lax', // 允許跨站請求攜帶 Cookie
b.example.com → 未登入 → 導向 a.example.com/login?source=https://b.example.com/dashboarda.example.com 輸入帳密 → 驗證成功 → 生成 Ticket 存入 Cache → 導向 https://b.example.com/dashboard?ticket=xxxb.example.com 中介層攔截 Ticket → 呼叫 a.example.com/auth/check-ticket → 驗證成功 → 本地登入用戶 → 重導至原頁面