从伪单点到真跨域:扒光 SSO(单点登录)的底层逻辑

6 阅读7分钟

前言 在企业级开发和微服务架构中,SSO(Single Sign-On,单点登录) 是一个绕不开的核心话题,也是高级后端面试的必考题。 很多人把 SSO 和 OAuth 2.0 混为一谈,或者只停留在“共享 Redis + Cookie”的初级阶段。这篇文章,我们将抛弃枯燥的官方文档,从 HTTP 协议、浏览器同源策略以及系统架构演进的角度,带你硬核剖析 SSO 到底是怎么解决跨域信任难题的。


一、 痛点与死穴:SSO 究竟在对抗什么?

在微服务或多系统架构初期,每个系统(如 OA、Jira、报表平台)往往各自为战。 从底层看:每个系统都有自己的 user 表,并在自己的内存或独立 Redis 里维护着一套独立的 Session

这就导致了两个极其棘手的工程灾难:

  1. 用户侧的“密码疲劳”:每天早上打开电脑,要在 5 个系统里敲 5 次账号密码。
  2. 运维侧的“账号孤岛”:新员工入职,IT 要跑 5 个系统的库去 INSERT 账号;员工离职,只要漏删了一个系统的账号,就是严重的安全事故(P0 故障)。

为了解决这个问题,我们需要一套“一处登录,处处通行”的机制。但作为程序员,你遇到的第一座大山就是:浏览器的同源策略(Same-Origin Policy)

HTTP 本身是无状态的,维持登录态全靠 Cookie 里的 Session ID但 Cookie 是严格绑定域名的。 你在 a.com 登录成功,浏览器把 Cookie 种在了 a.com 下。当你跳转到 b.com 时,浏览器绝对不可能a.com 的 Cookie 带过去。b.com 的后端拿不到 Session ID,自然认为你没登录。

核心结论: SSO 的本质,不是简单地写个统一登录页面,而是要设计一套受信任的中央凭证流转机制。通过合法的 HTTP 重定向和后端通信,巧妙地绕过浏览器的跨域 Cookie 限制,最终在各个独立的系统中分别建立起局部的会话状态。


二、 架构基石:两大绝对主角

为了实现这套机制,我们必须对现有的系统架构进行角色拆分。不管你用什么协议(CAS、OIDC、SAML),这两个概念必须烂熟于心:

  1. IdP (Identity Provider - 身份提供商)
    • 定位:全公司唯一的认证中心(大当家)。
    • 特征:全网唯一拥有登录页面的系统;唯一直接连接核心用户数据库的节点;负责全局登录态的维护和票据的签发。
  2. SP (Service Provider - 服务提供商)
    • 定位:各个具体的业务子系统(如 Jira、OA)。
    • 特征彻底废弃自己的登录页面。它们自己不校验密码,只要发现请求没有携带局部 Session,立刻将用户无情地 HTTP 302 重定向到 IdP 去验明正身。

三、 演进路线 1:伪 SSO(同父域下的偷懒做法)

在接触真正复杂的跨域协议之前,很多开发者都写过一种“偷懒版 SSO”。

架构前提:所有系统都挂在同一个顶级域名 .company.com 下(如 oa.company.comgit.company.com)。

技术实现(共享 Domain Cookie + 集中式 Redis):

  1. 用户访问 oa,未登录,跳到 sso.company.com 输入密码。
  2. SSO 后端验证通过,生成一个全局唯一的 COMPANY_TOKEN,存入一个全局共享的 Redis 集群中。
  3. SSO 后端在 HTTP 响应头中下发 Cookie,关键在于指定 Domain
    Set-Cookie: COMPANY_TOKEN=uuid-1234; Domain=.company.com; Path=/; HttpOnly
    
  4. 用户访问 git 系统,浏览器因为同父域的规则,自动带上了 COMPANY_TOKEN
  5. git 系统的后端拦截器拿到 Token,去那个共享的 Redis 集群里查,发现存在,直接放行!

为什么它是“伪”SSO,且注定被淘汰?

  • 致命缺陷 1(无法跨主域):一旦公司收购了新团队(域名是 startup.io),或者接入了外部云服务(aliyun.com),.company.com 的 Cookie 根本无法穿透过去,方案瞬间瘫痪。
  • 致命缺陷 2(架构高耦合):几十个微服务必须直连同一个物理 Redis 集群。一旦 Redis 宕机,全公司系统集体完蛋。且不同语言(Java/Go/PHP)编写的系统难以统一反序列化同一个 Session 对象。

四、 演进路线 2:真跨域 SSO —— CAS 经典工作流

为了解决完全跨域的问题,业界诞生了真正的跨域 SSO 协议。最经典的代表就是 CAS (Central Authentication Service)

CAS 的精髓在于:各个 SP 绝对独立,互不共享 Cookie,也不共享 Redis。它们只信任 IdP 签发的票据。

1. CAS 的两大“法宝票据”

在深入流程前,必须死死记住这两个核心票据(面试高频考点):

  • TGT (Ticket Granting Ticket - 票据授权票据)
    • 定位:用户的全局会话凭证
    • 存在哪:它是 IdP(认证中心)域名下的一个 Cookie。只要它存在,证明用户在 SSO 中心是已登录状态。
  • ST (Service Ticket - 服务票据)
    • 定位:IdP 专门签发给某个 SP 的单次入场券
    • 特点:一次性、极短效(通常几秒钟过期)、只能在 URL 参数里闪现一次(类似 OAuth 2.0 里的 Code)。

2. 核心工作流:完美的 5 步曲

请在脑海中跟着走一遍这个完全跨域的流转过程。 假设:IdP 是 sso.com,系统A 是 app1.com,系统B 是 app2.com

【阶段一:首次登录系统 A】

  1. 拦截重定向:用户访问 app1.com,SP-A 发现无局部 Session,响应 HTTP 302: Location: https://sso.com/login?service=https://app1.com/callback
  2. 种下全局 TGT:用户在 sso.com 页面输入密码。IdP 验证成功,在 sso.com 域名下种下 Cookie(这就是 TGT,全局登录态建立!),同时为 app1 签发一个 ST。
  3. 带票重定向回跳:IdP 响应 302,把用户踢回 app1.com,URL 附带 ST: Location: https://app1.com/callback?ticket=ST-abcde
  4. 幕后校验(核心安全步骤):SP-A 的前端拿到 ST-abcde 传给后端。SP-A 后端在服务器后台,发起 HTTP 请求直接调用 sso.com 的接口:“我拿到一个 ST,合法吗?”。
  5. 建立局部 Session:IdP 回复“合法,是张三”。SP-A 后端立刻在自己系统的内存/Redis 中为张三建立起局部 Session,并在 app1.com 下种下自己的 Session Cookie。用户成功进入系统 A。

【阶段二:见证单点登录 (SSO) 的魔法】

用户在同一个浏览器新开页签,访问系统 B (app2.com)。

  1. 再次拦截:SP-B 发现无局部 Session,302 重定向去 IdP: Location: https://sso.com/login?service=https://app2.com/callback
  2. ⚡ 魔法生效(发现 TGT):浏览器跳转到 sso.com 时,自动带上了之前种下的 TGT Cookie!IdP 一看请求头:“哟,带着合法的 TGT,证明已登录,免输密码!”。IdP 直接为 app2 签发一个新的 ST。
  3. 重复验证:IdP 将浏览器 302 踢回 app2.com/callback?ticket=ST-xyz987。SP-B 后端去 IdP 验证 ST,验证通过,建立 SP-B 的局部 Session。

结果:用户在访问 app2.com 时,只经历了几次极快的重定向闪烁,根本没有看到登录框,就直接进去了!

架构真理: 现代跨域 SSO 的底层架构模型,本质上就是:1 个全局会话 (IdP 的 TGT) + N 个局部会话 (各个 SP 自己独立的 Session)


五、 终极对比:OAuth 2.0 vs CAS

很多开发者学完 SSO 后,会觉得它和 OAuth 2.0 拿 Code 换 Token 的流程惊人地相似。面试官也极度喜欢问两者的区别。记住下面这张表,足以应对绝大多数架构面试:

维度OAuth 2.0 (授权码模式)CAS (跨域 SSO)
核心解决的问题委托授权(安全地给第三方发 API 访问凭证)跨域认证(打破信息孤岛,实现一次登录到处走)
URL 里闪现的临时票据code (授权码)ST (服务票据)
后端去换取的最终结果access_token (为了去调别人的 API 接口)用户信息 (为了在自己家建立局部 Session)
是否关心局部 Session不关心(OAuth 是无状态的)极度关心(SP 验证完 ST 后必须自己建立会话)

六、 结语

从同域名的共享 Cookie,到完全跨域的 CAS 票据流转,SSO 的演进是一部与浏览器安全策略(SOP)不断博弈的历史。

虽然 CAS 协议严谨且经典,但在如今前后端分离(SPA)和微服务横行的时代,基于 XML 且强依赖 302 重定向的 CAS 显得越来越笨重。现代企业架构,正在全面拥抱将 OIDC(OpenID Connect)作为微服务 SSO 的终极解决方案

但无论协议如何变迁,只要你搞懂了“全局会话与局部会话的分离”以及“后端安全换取票据”的底层逻辑,看任何认证源码都将如鱼得水。