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

PHP 實現 SSO 單點登錄接入完整流程(基於 Ticket 驗證模式)

     編輯:PHP 綜合知識

SSO(Single Sign On)讓用戶在多系統間一次登錄、處處可用。最常見的「集中式 Ticket 模式」:用戶訪問業務系統 → 重定向 SSO 登錄頁 → 登錄成功回調帶 ticket → 業務系統憑 ticket 換取用戶信息 → 本地創建/匹配用戶 → 登入。本文給出完整接入代碼與關鍵點。

核心接口契約

interface SSOLogin { 
    /** 獲取 SSO 授權頁面 URL,攜帶回調地址 */ 
    public function getAuthUrl(): string; 
    
    /** 憑 ticket 向 SSO 服務端換取用戶信息 
     * @return array ['username'=>'', 'truename'=>'', 'email'=>'', ...] 或空數組 
     */ 
    public function getInfoFromTicket(string $ticket): array; 
}

業務系統回調控制器

class SsoController extends Controller { 
    public function actionCheck() { 
        $ticket = Yii::$app->request->get('ticket'); 
        
        // 1. 無 ticket → 導向 SSO 登錄 
        if (!$ticket) { 
            return $this->render('auth_error', [ 
                'msg' => '請先授權登錄', 
                'loginUrl' => SSOLogin::getInstance()->getAuthUrl() 
            ]); 
        } 
        
        // 2. 換取用戶信息 
        $userInfo = SSOLogin::getInstance()->getInfoFromTicket($ticket); 
        if (empty($userInfo['username'])) { 
            return $this->render('auth_error', [ 
                'msg' => '授權失效,請重新登錄', 
                'loginUrl' => SSOLogin::getInstance()->getAuthUrl() 
            ]); 
        } 
        
        // 3. 本地用戶匹配/創建 
        $username = $this->normalizeUsername($userInfo['username']); 
        $user = User::find()->where(['username' => $username])->andWhere(['status' => User::STATUS_ACTIVE])->one(); 
        
        if (!$user) { 
            $user = new User([ 
                'username' => $username, 
                'email' => $userInfo['email'] ?? $username . '@sso.local', 
                'realname' => $userInfo['truename'] ?? '', 
                'role' => User::ROLE_DEV, 
                'is_email_verified' => 1, 
                'status' => User::STATUS_ACTIVE, 
            ]); 
            $user->setPassword(bin2hex(random_bytes(16))); // 隨機密碼,純 SSO 登錄 
            $user->generateAuthKey(); 
            $user->save(); 
        } 
        
        // 4. 登入(記住 30 天) 
        if (Yii::$app->user->login($user, 3600 * 24 * 30)) { 
            return $this->redirect('/task/index'); 
        } 
        
        return $this->render('auth_error', ['msg' => '登入失敗,請聯繫管理員']); 
    } 
    
    protected function normalizeUsername(string $raw): string { 
        // 統一處理:去除域前綴、特殊字符轉換等 
        return strtolower(preg_replace('/[^a-zA-Z0-9_-]/', '_', $raw)); 
    } 
}

SSO 客戶端 SDK 實現要點

class SSOLogin implements SSOLogin { 
    private $ssoServerUrl;  // 如 https://sso.company.com 
    private $appId; 
    private $appSecret; 
    private $callbackUrl;   // 業務系統回調地址 
    
    public function __construct($config) { 
        $this->ssoServerUrl = rtrim($config['server_url'], '/'); 
        $this->appId = $config['app_id']; 
        $this->appSecret = $config['app_secret']; 
        $this->callbackUrl = $config['callback_url']; 
    } 
    
    public function getAuthUrl(): string { 
        $params = [ 
            'app_id' => $this->appId, 
            'redirect_uri' => $this->callbackUrl, 
            'state' => bin2hex(random_bytes(16)), // CSRF 防護 
            'scope' => 'userinfo', 
        ]; 
        $_SESSION['sso_state'] = $params['state']; 
        return $this->ssoServerUrl . '/oauth/authorize?' . http_build_query($params); 
    } 
    
    public function getInfoFromTicket(string $ticket): array { 
        // 校驗 state 防 CSRF 
        if (($_SESSION['sso_state'] ?? '') !== ($_GET['state'] ?? '')) { 
            return []; 
        } 
        
        // 服務端驗證 ticket(通常調用 /oauth/userinfo 或 /api/ticket/verify) 
        $url = $this->ssoServerUrl . '/api/ticket/verify'; 
        $post = ['ticket' => $ticket, 'app_id' => $this->appId, 'app_secret' => $this->appSecret]; 
        
        $ch = curl_init($url); 
        curl_setopt_array($ch, [ 
            CURLOPT_POST => true, 
            CURLOPT_POSTFIELDS => json_encode($post), 
            CURLOPT_HTTPHEADER => ['Content-Type: application/json'], 
            CURLOPT_RETURNTRANSFER => true, 
            CURLOPT_TIMEOUT => 5, 
        ]); 
        $res = curl_exec($ch); 
        curl_close($ch); 
        
        $data = json_decode($res, true); 
        if (($data['code'] ?? -1) === 0 && !empty($data['data'])) { 
            return $data['data']; // ['username'=>'zhangsan', 'truename'=>'張三', 'email'=>'zhangsan@company.com'] 
        } 
        return []; 
    } 
}

完整交互時序圖

用戶          業務系統(A)          SSO服務端 
 |                |                      | 
 |--- 訪問 /task --->|                      | 
 |                |--- 302 /sso/check?ticket=... (無ticket) 
 |                |                      | 
 |                |<-- 302 SSO登錄頁 --| 
 |                |                      | 
 |--- 輸入賬號密碼 -->|                      | 
 |                |                      |--- 驗證用戶 --> 
 |                |                      |<-- 簽發 ticket --| 
 |                |                      | 
 |<-- 302 /sso/check?ticket=TICKET&state=STATE --| 
 |                |                      | 
 |                |--- 驗證 state -->| 
 |                |--- POST /api/ticket/verify (ticket+app_secret) -->| 
 |                |                      |--- 查詢用戶信息 --> 
 |                |                      |<-- 返回用戶信息 --| 
 |                |                      | 
 |                |--- 本地查找/創建用戶 --> 
 |                |--- 設置登錄 Session --> 
 |<-- 302 /task/index (登錄成功) --| 
 |                |                      |

關鍵安全點

  1. State 參數:防 CSRF,每次授權請求生成隨機 state,Session 存儲,回調校驗
  2. Ticket 一次性:SSO 服務端驗證後立即失效,防重放攻擊
  3. HTTPS 全鏈路:所有跳轉、API 呼叫強制 HTTPS
  4. App Secret 保護:僅服務端調用 /api/ticket/verify 時使用,不暴露前端
  5. 用戶信息最小化:僅獲取業務必要字段(username、realname、email、department)

單點登出(SLO)擴展