前言 在企业级开发和微服务架构中,SSO(Single Sign-On,单点登录) 是一个绕不开的核心话题,也是高级后端面试的必考题。 很多人把 SSO 和 OAuth 2.0 混为一谈,或者只停留在“共享 Redis + Cookie”的初级阶段。这篇文章,我们将抛弃枯燥的官方文档,从 HTTP 协议、浏览器同源策略以及系统架构演进的角度,带你硬核剖析 SSO 到底是怎么解决跨域信任难题的。
一、 痛点与死穴:SSO 究竟在对抗什么?
在微服务或多系统架构初期,每个系统(如 OA、Jira、报表平台)往往各自为战。
从底层看:每个系统都有自己的 user 表,并在自己的内存或独立 Redis 里维护着一套独立的 Session。
这就导致了两个极其棘手的工程灾难:
- 用户侧的“密码疲劳”:每天早上打开电脑,要在 5 个系统里敲 5 次账号密码。
- 运维侧的“账号孤岛”:新员工入职,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),这两个概念必须烂熟于心:
- IdP (Identity Provider - 身份提供商)
- 定位:全公司唯一的认证中心(大当家)。
- 特征:全网唯一拥有登录页面的系统;唯一直接连接核心用户数据库的节点;负责全局登录态的维护和票据的签发。
- SP (Service Provider - 服务提供商)
- 定位:各个具体的业务子系统(如 Jira、OA)。
- 特征:彻底废弃自己的登录页面。它们自己不校验密码,只要发现请求没有携带局部 Session,立刻将用户无情地 HTTP 302 重定向到 IdP 去验明正身。
三、 演进路线 1:伪 SSO(同父域下的偷懒做法)
在接触真正复杂的跨域协议之前,很多开发者都写过一种“偷懒版 SSO”。
架构前提:所有系统都挂在同一个顶级域名 .company.com 下(如 oa.company.com 和 git.company.com)。
技术实现(共享 Domain Cookie + 集中式 Redis):
- 用户访问
oa,未登录,跳到sso.company.com输入密码。 - SSO 后端验证通过,生成一个全局唯一的
COMPANY_TOKEN,存入一个全局共享的 Redis 集群中。 - SSO 后端在 HTTP 响应头中下发 Cookie,关键在于指定
Domain:Set-Cookie: COMPANY_TOKEN=uuid-1234; Domain=.company.com; Path=/; HttpOnly - 用户访问
git系统,浏览器因为同父域的规则,自动带上了COMPANY_TOKEN。 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】
- 拦截重定向:用户访问
app1.com,SP-A 发现无局部 Session,响应 HTTP 302:Location: https://sso.com/login?service=https://app1.com/callback - 种下全局 TGT:用户在
sso.com页面输入密码。IdP 验证成功,在sso.com域名下种下 Cookie(这就是 TGT,全局登录态建立!),同时为 app1 签发一个 ST。 - 带票重定向回跳:IdP 响应 302,把用户踢回
app1.com,URL 附带 ST:Location: https://app1.com/callback?ticket=ST-abcde - 幕后校验(核心安全步骤):SP-A 的前端拿到
ST-abcde传给后端。SP-A 后端在服务器后台,发起 HTTP 请求直接调用sso.com的接口:“我拿到一个 ST,合法吗?”。 - 建立局部 Session:IdP 回复“合法,是张三”。SP-A 后端立刻在自己系统的内存/Redis 中为张三建立起局部 Session,并在
app1.com下种下自己的 Session Cookie。用户成功进入系统 A。
【阶段二:见证单点登录 (SSO) 的魔法】
用户在同一个浏览器新开页签,访问系统 B (app2.com)。
- 再次拦截:SP-B 发现无局部 Session,302 重定向去 IdP:
Location: https://sso.com/login?service=https://app2.com/callback - ⚡ 魔法生效(发现 TGT):浏览器跳转到
sso.com时,自动带上了之前种下的 TGT Cookie!IdP 一看请求头:“哟,带着合法的 TGT,证明已登录,免输密码!”。IdP 直接为 app2 签发一个新的 ST。 - 重复验证: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 的终极解决方案。
但无论协议如何变迁,只要你搞懂了“全局会话与局部会话的分离”以及“后端安全换取票据”的底层逻辑,看任何认证源码都将如鱼得水。