单点登录
单点登录让外部身份提供方(IdP)决定谁可以登录你的 Control Center 服务器。系统支持 两种协议——SAML 2.0 与 OpenID Connect——两者最终都汇入同一个地方:一份全新签发的 设备凭据,它在所有下游环节都与邀请或二维码配对无从区分。
登录的形态
标题为“登录的形态”的部分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 服务中,一次性使用、寿命很短。
一次登录会校验什么
标题为“一次登录会校验什么”的部分在已验证的原生模块和服务内部,按顺序进行:
- 断言的签名(始终必需;响应信封的签名则视配置可选)——它在结构上能抵御签名 包裹,因为被校验的载荷与被验证的元素绑定在一起,
- 颁发者与配置的 IdP 匹配,
- 受众指向此服务提供方,
- 目的地是我们的 ACS URL,
InResponseTo与我们发出的某个待处理 AuthnRequest 匹配(未经请求、由 IdP 发起的响应默认关闭),- 有效期窗口在时钟偏差范围内成立(默认 90s),以及
- 断言 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 组不会 被存储。
- owner 角色无法通过 SSO 获得——既不能作为默认角色,也不能通过组映射获得。
- WebSocket 中继绝不是 SSO 通道;只能通过该中继访问服务器的手机,无法针对它完成 浏览器往返。