企業常擁有多個子系統(如 app.a.com、shop.a.com、admin.a.com),期望用戶在任一系統登入後,其餘系統自動識別登入狀態,無需重複輸入帳密。這即為單點登入需求。
前提:所有系統共享同一主域名(如 *.a.com)。
// 登入成功後,設定 Cookie domain 為主域名
setcookie('auth_token', $encryptedToken, [
'expires' => time() + 86400 * 7,
'path' => '/',
'domain' => '.a.com', // 關鍵:前綴點號表示所有子域
'secure' => true, // 僅 HTTPS
'httponly' => true, // 防 XSS 竊取
'samesite' => 'Lax', // CSRF 保護
]);
// 各子系統驗證
$token = $_COOKIE['auth_token'] ?? '';
if ($token && $user = validateToken($token)) {
// 自動登入
$_SESSION['user_id'] = $user['id'];
}
優點:無需額外服務、實作極簡。
缺點:僅限同主域名;安全性依賴 Token 加密強度;無法支援完全不同域名(如 b.com)。
用戶 → 子系統 A (未登入) → 重導向 → 登入中心 (sso.com)
↓ 登入成功
設定 sso.com Cookie
↓
回呼子系統 A 介面 /sso/callback?token=xxx
↓
子系統 A 驗證 Token、建立本地 Session
↓
用戶訪問子系統 B → 子系統 B 檢查本地 Session
無 → 請求登入中心驗證介面 → 有效則建立 Session
| 介面 | 方法 | 說明 |
|---|---|---|
/sso/login | GET | 登入中心頁面,帶 redirect_url 參數 |
/sso/verify | POST | 子系統攜帶 Token 請求驗證,回傳用戶資料 JSON |
/sso/logout | POST | 登出通知,子系統註冊回呼 URL,中心登出時批量通知 |
// 登入檢查中介層
function checkSsoLogin() {
if (isset($_SESSION['user_id'])) return $_SESSION['user_id'];
$token = $_GET['sso_token'] ?? $_COOKIE['sso_token'] ?? '';
if (!$token) {
// 重導向至登入中心
$redirect = urlencode('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
header("Location: https://sso.com/sso/login?redirect_url=$redirect");
exit;
}
// 驗證 Token
$user = verifyTokenWithSsoCenter($token);
if (!$user) {
// Token 無效,清除並重試
setcookie('sso_token', '', time()-3600, '/', '.a.com');
header('Location: ' . $_SERVER['REQUEST_URI']);
exit;
}
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_data'] = $user;
// 同步寫入本地 Cookie 供後續請求
setcookie('sso_token', $token, time()+86400*7, '/', '.a.com', true, true);
return $user['id'];
}
// 登入中心驗證介面
function verifyTokenWithSsoCenter($token) {
$ch = curl_init('https://sso.com/sso/verify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ['token' => $token],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => true,
]);
$res = curl_exec($ch);
curl_close($ch);
return $res ? json_decode($res, true) : false;
}
/sso/logout,攜帶系統識別碼登入中心簽發 JWT(含 sub, exp, aud, roles 等),子系統僅需驗證簽名與過期時間,無需呼叫中心驗證介面,極大降低耦合與延遲。
// 登入中心簽發
$payload = ['sub' => $userId, 'exp' => time()+3600, 'roles' => $roles];
$jwt = JWT::encode($payload, $privateKey, 'RS256');
// 子系統驗證 (需公鑰)
try {
$payload = JWT::decode($jwt, $publicKey, ['RS256']);
// 可選:檢查 aud、權限
$_SESSION['user_id'] = $payload->sub;
} catch (Exception $e) {
// 無效
}
Secure、HttpOnly| 場景 | 推薦方案 | 複雜度 |
|---|---|---|
| 同主域名多子系統 | 方案一:主域名 Cookie | 低 |
| 不同域名、傳統單體應用 | 方案二:中心化 SSO + 回呼 | 中 |
| 微服務、多語言、高併發 | 方案三:JWT 無狀態 | 中高 |
| 企業級、需整合 LDAP/OAuth2 | Keycloak / Auth0 / Casdoor 等成熟 IDP | 低(集成) |
無論選擇哪種方案,核心原則皆為:認證與業務解耦、Token 傳輸加密、最小權限、完整審計日誌。