跳到內容

單一登入

單一登入讓外部身分識別供應商(IdP)決定誰可以登入你的 Control Center 伺服器。系統支援兩種協定——SAML 2.0 與 OpenID Connect——而兩者最終都 匯入同一個地方:一份全新簽發的裝置憑證,它在所有下游環節都與邀請或 QR 配對無從區分。

SAML 登入以瀏覽器為中介。IdP 從不與你的伺服器建立連線;一切都由 使用者的瀏覽器傳遞:

browser ──> /saml/login ──> IdP ──POST SAMLResponse──> /saml/acs
│ verify
▼
provision user (maybe)
│
▼
browser handoff: device PSK in the URL
fragment (never a query parameter)

因為瀏覽器就是傳遞者,位於 NAT 之後的伺服器照樣能正常登入: localhost、LAN、Tailscale、VPN 或通道都可以——只有使用者的瀏覽器必須能 連上 ACS URL。手機遠端控制所用的 WebSocket 轉送無法承載這段 HTTP 往返, 這正是 SSO 進入點一律指向伺服器 HTTP 來源的原因。

OpenID Connect 遵循同樣的形態,改以授權碼 + PKCE 往返;宣告透過 TLS 從 簽發者的權杖端點讀取,因此瀏覽器完全不提供身分資料。

Control Center 刻意拆分職責:

組件 位置 負責
cc_saml 原生模組 packages/cc_natives/native/saml/ XML-DSig 驗證、正規化、SAML 設定檔驗證——委由固定版本的純 Rust saml crate 處理,位於無狀態 C ABI 之後
SamlService / OidcService cc_server_core 登入狀態(待處理請求追蹤器、重播去重)、驗證原則開關、共用的佈建呼叫
SsoProvisioner cc_server_core 尋找或建立使用者、釘選供應商主體、依自動成員原則授予成員身分
ScimService cc_server_core SCIM 2.0 推送介面(佈建/取消佈建生命週期)
SsoSettingsService cc_server_core 已儲存的連線資料列、即時重新套用、SCIM 權杖

系統刻意不提供純 Dart 的 SAML 加密後備方案:手寫的 XML-DSig/正規化 正是簽章包裹漏洞的溫床,因此缺少 cc_saml 程式庫會同時拒絕 SAML 登入與 伺服器啟動。原生模組在設計上是無狀態的——跨請求的狀態位於 Dart 服務中, 僅供單次使用且壽命短暫。

在驗證過的原生模組與服務內部,依序進行:

  1. 判斷提示的簽章(一律必要;回應封套的簽章則依設定為選用)——因為 受驗證的酬載與被驗證的元素相互綁定,它在結構上能抵禦簽章包裹,
  2. 簽發者符合設定的 IdP,
  3. 對象指向此服務提供者,
  4. 目的地是我們的 ACS URL,
  5. InResponseTo 符合我們發出的某個待處理 AuthnRequest(未經請求、由 IdP 起始的回應預設為關閉),
  6. 有效期間窗期落在時鐘偏差範圍內(預設 90s),以及
  7. 判斷提示 id 從未出現過(重播快取)。

失敗會以具型別的錯誤碼回傳——signature、expired、audience、destination、 issuer、request-match——而端點只回應籠統的錯誤,因此攻擊者無從得知偽造 為何失敗。

首次 SSO 登入會把供應商不可變的主體(issuer 加上 SAML NameID/OIDC sub,或 SCIM externalId)釘選到該帳戶上。此後,識別登入的是主體——而 不是電子郵件。若之後有人從同一個或另一個 IdP 提出相同的電子郵件,已釘 選的帳戶會拒絕他們。這正是防止供應商端被回收的電子郵件演變成帳戶接管的 關鍵。

JIT 佈建會在登入時進行(SsoProvisioner)。SCIM 是獨立的推送介面 (ScimService),會把同一個主體釘選到使用者資料列上;它不授予工作區 成員身分。取消佈建——SCIM active: false 或 DELETE——若該帳戶是工作區 擁有者便會拒絕(請先轉移)。否則它會先撤銷所有裝置憑證(透過裝置監看, 工作階段會在數秒內中斷),移除所有工作區成員身分,並將帳戶標記為已停用。 使用者資料列仍然保留:歸屬是永久的。重新啟用會恢復登入,但絕不會重新 授予成員身分。

  • 群組→角色對應在登入時進行,來源是 SAML 屬性/OIDC 宣告——推送的 SCIM 群組不會被儲存。
  • 擁有者角色無法透過 SSO 取得——既不能當作預設角色,也不能透過群組 對應取得。
  • WebSocket 轉送絕不是 SSO 通道;只能透過該轉送連上伺服器的手機,無法 對它完成瀏覽器往返。