谈谈 SSO(单点登录)的原理和优势

内容分享1周前发布
0 0 0

谈谈 SSO(单点登录)的原理和优势

这张图把 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 发现本地没有
gmail_session
cookie(或已过期)。

(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 回到
redirect_uri
,带 authorization code


302 Location: https://gmail.example.com/oidc/callback? code=AUTH_CODE &state=RANDOM_CSRF_TOKEN 

(6) Gmail validates token(实际是:用 code 换 token + 验 token)
Gmail 后端拿到
code
后,服务器到服务器调用 IdP 的 token 端点:


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 一致

(可选)
acr/amr
:是否满足 MFA/强度要求

(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 发现浏览器已经有
idp_session
cookie → 直接认为用户已登录(可能只做一次“同意/授权”或也不需要)。

(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,带一个
SAMLRequest
(通常 DEFLATE+Base64):


GET https://idp.example.com/sso? SAMLRequest=BASE64(XML(AuthnRequest)) &RelayState=RANDOM_STATE 

AuthnRequest 里大概包含:


Issuer
(SP 标识)


AssertionConsumerServiceURL
(断言要回传到哪里)


ID
(请求唯一 ID,防重放/关联响应)


Destination
(IdP 的 SSO 地址)

(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 证书)


InResponseTo
匹配之前 AuthnRequest 的 ID


AudienceRestriction
(这张断言只能给这个 SP 用)

验时间窗口:
NotBefore / NotOnOrAfter


Recipient
/
Destination
等防投递到错误端点


RelayState
防 CSRF/状态绑定

(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 全局退出要额外设计

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...