
这张图把 SSO(Single Sign-On,单点登录)画成了“三方接力”:用户/浏览器 ↔ 服务提供方 SP(如 Gmail、Slack) ↔ 身份提供方 IdP(如 Okta、Auth0)。核心思想就一句话:
应用不自己管“你是谁”,而是把认证交给 IdP;应用只负责“验票 + 发放本地会话”。
浏览器像“快递员”,在 SP 和 IdP 之间转交认证请求与令牌(token)。
1) 按图逐步走一遍:第一次登录 Gmail(1~7)
用户访问 Gmail(1):Gmail 发现你还没有自己的会话(no session)。
Gmail 重定向(2):把浏览器踢去“认证入口”,带上一个认证请求(auth request)。
浏览器把请求转发给 IdP(3):到 Okta/Auth0 这类统一登录中心。
IdP 显示登录页(4):你输入账号密码/短信/OTP/指纹等(可能有 MFA)。
IdP 建立自己的会话并签发 token(5):
IdP 给浏览器一份“票据”(token / assertion),同时 IdP 往往也会在你浏览器里种下 IdP 的会话 cookie(表示你在 IdP 已登录)。
浏览器再把 token 带回给 Gmail(图上写 Browser forwards token to Gmail)。
Gmail 验证 token(6):校验签名、有效期、受众(audience)、issuer 等。
Gmail 返回受保护资源(7):并且通常会再给你发一个 Gmail 自己的 session cookie,之后访问 Gmail 就不必每次都回 IdP 了。
2) SSO 的“魔法点”:再去 Slack 不用再输密码(8~15)
用户访问 Slack(8):Slack 发现你没有 Slack 的会话。
Slack 重定向到认证(9):同样发起 auth request。
浏览器把请求送到 IdP(10)。
IdP 直接跳过登录(11):因为你刚才登录 Gmail 时,IdP 的会话还在(IdP cookie 仍有效)。
IdP 签发给 Slack 用的新 token(12):注意通常是“针对 Slack 这个应用”的新票据(受众不同、scope/claims 可能不同)。
浏览器把 token 转交给 Slack(13)。
Slack 验证 token(14)。
Slack 放行(15):并建立 Slack 自己的本地会话。
所以你感觉像“登录一次,全家通行”,其实是:你在 IdP 那边的登录状态被复用了,各个应用只是在需要时向 IdP “换一张针对自己的票”。
3) 这里的 token 可能是什么?(常见协议)
SAML 2.0:token 常叫 SAML Assertion(企业传统 SSO 很常见)
OpenID Connect(OIDC):token 常见是 ID Token(JWT),配合 OAuth 2.0
Kerberos/NTLM:更多在内网/AD 环境(另一种 SSO 路线)
不管哪种,SP 验证 token 的共通点基本都是:
签名正确(IdP 的公钥/证书)
未过期(exp / NotOnOrAfter)
颁发者正确(issuer)
受众是我(audience = 这个 SP)
防重放/关联请求(state/nonce 等,防 CSRF、重放)
4) SSO 的优势(为什么企业爱用)
对用户
少记密码、少登录:体验提升最明显(一次登录,多应用可用)
更容易上 MFA / 无密码:登录入口集中在 IdP,体验更一致
对安全
降低“密码复用/弱密码”风险:应用不再各自存密码(或至少不再依赖本地密码)
统一策略:比如强制 MFA、设备合规、IP/地理位置、风险评分、条件访问
账号生命周期管理更靠谱:员工入职/离职,在 IdP 一处开关就能影响所有接入的应用(配合 SCIM 做自动开通/回收更爽)
审计更集中:谁在何时登录了哪些应用、失败原因等更好追踪
对开发/运维
应用侧变简单:SP 主要做“跳转 + 验票 + 建本地 session”,不必自己实现复杂认证体系
减少重置密码工单:helpdesk 压力明显下降
5) 也要知道的代价/坑(现实里一定会遇到)
IdP 成为“单点”:IdP 挂了,所有应用登录都受影响(需要高可用、容灾)
“一次失守,处处沦陷”:IdP 会话/账号被盗,影响面更大(所以更要 MFA、风控、短会话、设备绑定等)
单点登出(SLO)很麻烦:退出一个应用不等于退出所有(浏览器 cookie、前后端通道、多协议混用都让它复杂)
接下来把图里的 1~7、8~15 两段流程,分别对照到最常见的两套实现:OIDC(OAuth2 + OpenID Connect,现代 Web/移动端首选) 和 SAML 2.0(企业传统 SSO 常见),并把“重定向时到底带了什么参数 / token 里有什么 / SP 如何验票”讲清楚。
SAML 2.0 的实现细节
SAML(Security Assertion Markup Language)2.0 是一种基于XML的开放标准,用于在不同域之间交换认证和授权数据,常用于企业SSO场景,如Microsoft Entra ID或AWS IAM Identity Center。它支持Web浏览器SSO配置文件,允许服务提供者(SP)和身份提供者(IdP)之间安全通信。
关键组件
身份提供者(IdP):负责用户认证并生成断言(Assertion),如Auth0或Okta。服务提供者(SP):应用或服务,依赖IdP验证用户,如企业内部工具。用户代理(User Agent):通常是浏览器,处理重定向。断言(Assertion):XML文档,包含用户身份、属性和认证细节,受数字签名保护。绑定(Bindings):定义消息传输方式,如HTTP Redirect(轻量级,用于浏览器重定向)或HTTP POST(用于更大负载)。
主要流程(Web浏览器SSO)
SAML支持SP-initiated和IdP-initiated两种发起方式:
SP-initiated SSO:
用户访问SP,受保护资源触发认证请求。SP生成SAML AuthnRequest(XML),通过浏览器重定向到IdP(使用HTTP Redirect绑定)。IdP提示用户登录(用户名/密码、MFA等),验证后生成SAML Response,包括签名的Assertion。Response通过浏览器POST回SP。SP验证签名、断言有效性(检查Issuer、Audience、时间戳),然后创建本地会话并授予访问。
IdP-initiated SSO:
用户先在IdP登录,IdP生成未请求的Assertion并重定向到SP。SP验证Assertion并登录用户。
实施步骤
配置元数据交换:IdP和SP交换XML元数据文件,包含端点URL、公钥和支持的绑定。处理断言:使用库如Python的PySAML2或Java的OpenSAML解析XML。确保时钟同步以防NotBefore/NotOnOrAfter条件失效。单点登出(SLO):IdP发送LogoutRequest到所有SP,SP销毁会话并响应LogoutResponse。安全考虑:使用X.509证书签名/加密Assertion,防范XML签名绕过攻击;启用HTTPS;限制Audience以防重放攻击。
SAML常与WS-Federation结合,用于Windows环境,但XML格式使其较重,适合B2B联邦身份
A) 用 OIDC(Authorization Code + PKCE)把图走一遍(推荐做法)
角色映射:
SP = Gmail/Slack(应用)
IdP = Okta/Auth0(统一登录中心)
Browser = “搬运工”,只负责跟着 302 跑、带参数/带 cookie
1~7:第一次访问 Gmail(未登录)
(1) User visits Gmail(无 session)
Gmail 发现本地没有 cookie(或已过期)。
gmail_session
(2) Gmail redirects with auth request
Gmail 返回 302 到 IdP 的授权端点(Authorization Endpoint),URL 大概长这样:
GET https://idp.example.com/oauth2/v1/authorize? client_id=gmail_client_id &response_type=code &scope=openid%20profile%20email &redirect_uri=https://gmail.example.com/oidc/callback &state=RANDOM_CSRF_TOKEN &nonce=RANDOM_REPLAY_GUARD &code_challenge=BASE64URL(SHA256(code_verifier)) &code_challenge_method=S256
这些参数的意义:
client_id / redirect_uri:告诉 IdP “哪个应用在申请登录、回调地址是哪里”
scope=openid …:表示这是“登录”(OIDC)而不只是“授权”
state:防 CSRF(回来的 state 必须原样一致)
nonce:防 token 重放(写进 id_token 里,回来必须匹配)
PKCE:防“授权码被截获后换 token”(公有客户端/浏览器环境强烈建议)
(3) browser forwards auth request → IdP
浏览器跟着 302 去 IdP;此时 IdP 可能还没会话 cookie。
(4) IdP displays login + user submits credentials
IdP 展示登录页 + MFA 等。
成功后 IdP 会在浏览器种下 IdP 自己的会话 cookie(比如 )。
idp_session
(5) IdP creates session + issues token to browser(更准确:先发“code”)
在 OIDC 推荐流程里,IdP 并不直接把 access token/id token 都塞前端,而是:
IdP 302 回到 ,带 authorization code:
redirect_uri
302 Location: https://gmail.example.com/oidc/callback? code=AUTH_CODE &state=RANDOM_CSRF_TOKEN
(6) Gmail validates token(实际是:用 code 换 token + 验 token)
Gmail 后端拿到 后,服务器到服务器调用 IdP 的 token 端点:
code
POST https://idp.example.com/oauth2/v1/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& client_id=gmail_client_id& redirect_uri=https://gmail.example.com/oidc/callback& code=AUTH_CODE& code_verifier=ORIGINAL_PKCE_VERIFIER
IdP 返回:
{ "id_token": "JWT...", "access_token": "JWT or opaque...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "..." // 视策略而定 }
Gmail 验证重点(验票):
JWT 签名:用 IdP 的 JWK 公钥验 签名
id_token
iss / aud:issuer 必须是这个 IdP;aud 必须包含 gmail_client_id
exp/iat:未过期
nonce:与最初发出去的 nonce 一致
(可选):是否满足 MFA/强度要求
acr/amr
(7) returns protected resources
Gmail 建立自己的会话:
Set-Cookie: gmail_session=...; HttpOnly; Secure; SameSite=Lax
之后用户访问 Gmail 资源就不用再去 IdP 了(直到过期)。
8~15:再访问 Slack(SSO 体验出现)
(8)(9)(10) Slack 也发起 authorize 重定向(同上,但 client_id / redirect_uri 是 Slack 的)
(11) skips login process
IdP 发现浏览器已经有 cookie → 直接认为用户已登录(可能只做一次“同意/授权”或也不需要)。
idp_session
(12) issues new token(同样:返回 code,然后 Slack 后端换 token)
IdP 仍然给 Slack 发 针对 Slack 的 code/token(aud/claims/scope 与 Gmail 不同)
(14)(15) Slack validates token + grant access
Slack 用自己的 client_id、回调地址、JWK 验签等完成验票 → 发 Slack 自己的 session cookie。
你看到的“单点登录”,本质是:IdP 会话复用 + 每个 SP 仍然各自建本地 session。
B) 如果用 SAML 2.0(经典企业 SSO)对应图的报文长什么样
SAML 里不叫 token/JWT,叫 Assertion(断言),常见是 XML,签名用证书。
1~7(SP 发起:SP-initiated SSO)
(2) SP redirects
Gmail 把浏览器重定向到 IdP 的 SSO URL,带一个 (通常 DEFLATE+Base64):
SAMLRequest
GET https://idp.example.com/sso? SAMLRequest=BASE64(XML(AuthnRequest)) &RelayState=RANDOM_STATE
AuthnRequest 里大概包含:
(SP 标识)
Issuer
(断言要回传到哪里)
AssertionConsumerServiceURL
(请求唯一 ID,防重放/关联响应)
ID
(IdP 的 SSO 地址)
Destination
(4)(5) IdP 登录后返回 SAMLResponse
IdP 302/POST 回到 SP 的 ACS(Assertion Consumer Service),通常是浏览器自动提交一个表单:
<form action="https://gmail.example.com/saml/acs" method="POST"> <input name="SAMLResponse" value="BASE64(XML(Response+Assertion))" /> <input name="RelayState" value="RANDOM_STATE" /> </form>
(6) SP validates(SAML “验票”要点)
验 XML 签名(IdP 证书)
验 匹配之前 AuthnRequest 的 ID
InResponseTo
验 (这张断言只能给这个 SP 用)
AudienceRestriction
验时间窗口:
NotBefore / NotOnOrAfter
验 /
Recipient 等防投递到错误端点
Destination
验 防 CSRF/状态绑定
RelayState
(7) SP 建会话并放行
SP 发自己的 session cookie,之后跟 OIDC 一样走本地会话。
C) 你实现/排错时最关键的 6 个“抓手”(非常实用)
浏览器里同时存在两类 cookie
IdP cookie(决定“是否跳过登录”)
各 SP cookie(决定“是否已登录该应用”)
“SSO 跳过登录”靠的是 IdP 会话,不是某个 SP 的会话
所以清了 IdP cookie,SSO 就没了
清了某个 SP cookie,只会让该 SP 重新走一遍“向 IdP 要票”的流程
state(OIDC)/RelayState(SAML)是防 CSRF 的生命线
不一致就必须拒绝
audience 是“这张票给谁用”
Slack 的票不能拿去登录 Gmail(否则就是严重漏洞)
时间窗口(exp / NotOnOrAfter)与时钟漂移
生产里经常因为机器时间偏差导致“偶发登录失败”
退出(Single Logout)比登录难
多数系统只能做到“尽量退出当前 SP”,IdP 全局退出要额外设计