首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP SSO 單點登入實作:跨域 Cookie 傳遞、Callback 機制、Iframe 心跳檢測完整流程

     編輯:PHP 綜合知識

單點登入 (SSO) 讓用戶在多個子系統 (App1, App2, ...) 中**僅需登入一次**,即可存取所有受信任系統。本文實作基於 **Cookie + Callback + Iframe 心跳** 的經典 PHP SSO 架構,涵蓋:

  1. 統一登入頁 (SSO Server)
  2. 子系統發起登入攜帶 Callback URL
  3. 登入成功後跨域設定 Cookie (Callback 頁面)
  4. 子系統驗證本地 Cookie 完成登入
  5. Iframe 定時檢測 SSO 登入狀態 (單點登出同步)

架構角色

  • SSO Server:`sso.example.com` (獨立域名,負責認證、簽發 Token)
  • App1:`app1.example.com` (子系統 A)
  • App2:`app2.example.com` (子系統 B)
  • 共享父域:`example.com` (Cookie Domain 設為 `.example.com` 實現跨子域共享)

1. 子系統首頁 (檢查本地登入、發起 SSO 登入)

<?php
// app1.example.com/index.php
session_start();
$ssoServer = 'https://sso.example.com';
$appUrl = 'https://app1.example.com';

// 1. 檢查本地 Session
if (isset($_SESSION['user'])) {
    echo "歡迎,{$_SESSION['user']['name']} | 登出";
} else {
    // 2. 未登入:導向 SSO 登入頁,攜帶 callback
    $callback = $appUrl . '/sso_callback.php';
    $loginUrl = $ssoServer . '/login.php?callback=' . urlencode($callback);
    echo "請登入";
}

// 3. 嵌入 Iframe 心跳檢測 SSO 狀態 (隱藏、零尺寸)
$heartbeatUrl = $ssoServer . '/heartbeat.php?callback=' . urlencode($appUrl . '/sso_heartbeat.php');
echo "";

2. SSO 登入頁 (sso.example.com/login.php)

<?php
// sso.example.com/login.php
session_start();
header('Content-Type: text/html; charset=utf-8');

$callback = $_GET['callback'] ?? '';

// 處理登出
if (isset($_GET['logout'])) {
    // 清除 SSO Session
    $_SESSION = [];
    session_destroy();
    // 清除 SSO Cookie (父域)
    setcookie('sso_token', '', time() - 3600, '/', '.example.com', true, true);
    // 重導回原系統首頁
    header('Location: ' . $callback);
    exit;
}

// 已有 SSO Session,直接回調
if (isset($_SESSION['sso_user'])) {
    $user = $_SESSION['sso_user'];
    // 簽發一次性 Token 給 Callback
    $token = bin2hex(random_bytes(32));
    $_SESSION['sso_tokens'][$token] = $user;
    header('Location: ' . $callback . '?sso_token=' . $token);
    exit;
}

// 顯示登入表單
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';

    // 驗證帳密 (示範)
    $valid = ($username === 'demo' && $password === '123456'); // 實際查資料庫 password_verify

    if ($valid) {
        $user = ['id' => 1, 'name' => $username, 'email' => "$username@example.com"];
        $_SESSION['sso_user'] = $user;

        // 設定 SSO Cookie (父域,所有子系統可讀)
        setcookie('sso_token', bin2hex(random_bytes(32)), [
            'expires' => time() + 3600, // 1 小時
            'path' => '/',
            'domain' => '.example.com',
            'secure' => true,
            'httponly' => true,
            'samesite' => 'Lax',
        ]);

        // 簽發一次性 Token 回調
        $token = bin2hex(random_bytes(32));
        $_SESSION['sso_tokens'][$token] = $user;
        header('Location: ' . $callback . '?sso_token=' . $token);
        exit;
    } else {
        $error = '帳號或密碼錯誤';
    }
}
?>

SSO 登入

統一身份認證



3. 子系統 Callback 頁面 (接收 Token、建立本地 Session)

<?php
// app1.example.com/sso_callback.php
session_start();
header('Content-Type: text/html; charset=utf-8');

$token = $_GET['sso_token'] ?? '';
if (!$token) {
    die('缺少 SSO Token');
}

// 向 SSO Server 驗證 Token (Server-to-Server,安全)
$ssoVerifyUrl = 'https://sso.example.com/verify_token.php?token=' . urlencode($token);
$ch = curl_init($ssoVerifyUrl);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5]);
$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
if (!$data || empty($data['user'])) {
    die('SSO Token 無效或已過期');
}

// 建立本地 Session
$_SESSION['user'] = $data['user'];
$_SESSION['sso_token'] = $token; // 保留以便心跳驗證

// 導向首頁
header('Location: /index.php');
exit;

4. SSO Token 驗證端點 (sso.example.com/verify_token.php)

<?php
// sso.example.com/verify_token.php
session_start();
header('Content-Type: application/json; charset=utf-8');

$token = $_GET['token'] ?? '';
if (!$token || empty($_SESSION['sso_tokens'][$token])) {
    echo json_encode(['error' => 'invalid_token']);
    exit;
}

// 一次性 Token:驗證後立即銷毀
$user = $_SESSION['sso_tokens'][$token];
unset($_SESSION['sso_tokens'][$token]);

echo json_encode(['user' => $user]);

5. Iframe 心跳檢測 (單點登出同步)

SSO 端 heartbeat.php:

<?php
// sso.example.com/heartbeat.php
session_start();
header('Content-Type: text/html; charset=utf-8');

$callback = $_GET['callback'] ?? '';

if (isset($_SESSION['sso_user'])) {
    // 已登入:產生一次性 Token 讓子系統更新心跳
    $token = bin2hex(random_bytes(16));
    $_SESSION['heartbeat_tokens'][$token] = true;
    $url = $callback . '?heartbeat_token=' . $token;
} else {
    // 未登入:通知子系統登出
    $url = $callback . '?logout=1';
}
?>




子系統心跳接收端 sso_heartbeat.php:

<?php
// app1.example.com/sso_heartbeat.php
session_start();

if (isset($_GET['logout'])) {
    // SSO 已登出,清除本地 Session
    session_destroy();
    exit;
}

$token = $_GET['heartbeat_token'] ?? '';
if ($token) {
    // 可選:向 SSO 驗證 heartbeat_token (省略)
    // 更新本地 Session 過期時間
    $_SESSION['last_heartbeat'] = time();
}
exit;

6. 子系統登出 (同步登出 SSO)

<?php
// app1.example.com/logout.php
session_start();

// 1. 清除本地 Session
session_destroy();

// 2. 導向 SSO 登出頁 (SSO 會清除自己的 Session 與 Cookie)
header('Location: https://sso.example.com/login.php?logout=1&callback=' . urlencode('https://app1.example.com/'));
exit;

安全性關鍵點

風險對策
Callback URL 開放重導向SSO 端維護**白名單**,僅允許預註冊的子系統域名。
Token 被竊取重放Token **一次性**、短效期 (30 秒)、HTTPS 傳輸、HttpOnly Cookie。
CSRF 攻擊登入/登出登入表單加入 CSRF Token;登出需 POST 或確認頁。
Iframe 點擊劫持SSO 頁面發送 `X-Frame-Options: SAMEORIGIN` 或 `Content-Security-Policy: frame-ancestors https://app1.example.com https://app2.example.com`。
Session 固定攻擊登入成功後 `session_regenerate_id(true)`。

現代化替代方案

  • OAuth 2.0 / OpenID Connect (OIDC):業界標準,使用 Authorization Code Flow + PKCE,支援 Refresh Token、ID Token (JWT)、UserInfo 端點。推薦使用 league/oauth2-server 或 Keycloak 現成身份提供者。
  • SAML 2.0:企業級聯盟身份,XML 簽名加密,適合與 ADFS、Azure AD 整合。
  • JWT Stateless SSO:SSO 簽發簽名 JWT (HS256/RS256),子系統僅驗證簽名無需回調 SSO,適合微服務、高併發。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved