SAML 2.0 与遗留 SSO:企业身份联邦的协议原理、迁移策略与 OIDC 对比

协议详解 · 2026-07-07

概述

2026 年了,SAML 2.0 这个诞生于 2005 年的标准在 OIDC 的压力下看似日薄西山,但据 Gartner 和 Okta 的统计,全球超过 70% 的企业 SaaS 产品仍然把 SAML SSO 放在 Enterprise 定价方案的第一行。当你做 B2B SaaS,第一个要求"接 SSO"的企业客户——通常是 500 人以上的中大型公司——发给你的可能不是 OIDC metadata URL,而是一个 XML 格式的 Federation Metadata 文件。

SAML(Security Assertion Markup Language)的核心思想比 OAuth/OIDC 更简单:身份提供方(IdP,Identity Provider)签发一个 XML 格式的断言(Assertion),服务提供方(SP,Service Provider)验签后信任其中的身份信息。但也正是这种基于 XML 的"简单",使得 SAML 在工程实现上比基于 JSON 的 OIDC 复杂得多——XML 签名验证、NameID 格式选择、Metadata 同步等环节都潜藏着大量工程陷阱。

本文的目标不是争论 SAML 与 OIDC 孰优劣,而是面向实际工程场景:

  • 为需要对接存量企业 SAML IdP 的开发者提供协议详解
  • 为正在做身份系统现代化的架构师提供迁移策略参考
  • 为安全工程师提供 SAML 协议的安全分析与攻击面梳理

协议模型与核心概念

关键角色映射

SAML 和 OIDC 在概念层是同构的——都解决"IdP 告诉 SP 用户是谁"——但协议细节天差地别。以下是两种协议的角色映射:

SAML 角色OIDC 对应职责
IdP(Identity Provider)OP(OpenID Provider)认证用户,签发 SAML Assertion
SP(Service Provider)RP(Relying Party)接收 Assertion,建立本地会话
PrincipalUser被认证的用户
AssertionID Token包含用户身份信息的安全声明
MetadataDiscovery Document技术配置交换(端点、证书、能力集)

SAML 协议栈结构

SAML 2.0 不是一个单一协议,而是一个由多个子规范组成的协议栈:

这种分层设计让 SAML 具有良好的灵活性,但也意味着实现者需要同时处理多个规范,工程复杂度远高于 OIDC 的"全在 HTTP+JSON 层解决"模式。

两种 SSO 流程详解

SP-Initiated SSO(服务提供方发起)

SP-Initiated 是最常见的 SAML SSO 流程,由 SP 发起认证请求:

CODE
用户 → SP → IdP → 用户 → SP → 本地会话建立

详细交互序列:

AuthnRequest 的典型格式:

XML
<samlp:AuthnRequest
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
  ID="_abc123"
  Version="2.0"
  IssueInstant="2026-07-07T10:00:00Z"
  Destination="https://idp.example.com/sso"
  AssertionConsumerServiceURL="https://sp.example.com/acs"
  ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST">
  <saml:Issuer>https://sp.example.com</saml:Issuer>
</samlp:AuthnRequest>
工程要点:AuthnRequest 经过浏览器 URL 重定向时需要做 Base64 编码 + URL 编码。由于完整的 XML 可能长达数 KB,部分浏览器对 URL 长度有限制(通常 2KB~8KB),实践中往往采用 DEFLATE 压缩后 Base64 编码以减小体积。

IdP-Initiated SSO(身份提供方发起)

用户已登录 IdP,在 IdP 门户点击 SP 的图标,IdP 直接生成 SAML Response POST 到 SP ACS URL。

CODE
用户 → IdP(已有会话)→ SP → 本地会话建立

关键差异与风险:

  • 缺少 AuthnRequest 上下文:SP 是被动接收方,无法传递 RelayState(目标页面),IdP 也不会知道用户在 SP 中想去哪里
  • CSRF 保护缺失:IdP-Initiated SSO 不在 SP-Initiated 的 CSRF 保护范围之内——SP 没有发出过 AuthnRequest,因此无法通过 InResponseTo 字段做请求-响应绑定
  • Replay 风险:如果 SP 没有额外的防重放措施(如对 Assertion ID 做去重),攻击者可以重放一个截获的 SAML Response 来登录
防御建议:商用 IdP 通常给 Assertion 设置很短的 NotOnOrAfter 有效期(一般 5 分钟以内),提供了有限的防护窗口。但 SP 仍应记录 Assertion ID 并拒绝重复使用,或要求 IdP-Initiated 流程必须携带额外的签名参数。

SAML Assertion 结构与验证

Assertion 的核心字段

SP 验证清单

SP 收到 SAML Response 后,必须执行以下验证步骤:

工程陷阱:XML 签名包装攻击(XSW):SAML 的 XML 签名验证逻辑如果实现不当,攻击者可以通过移动 Signature 元素到文档其他位置,让 SP 验证的是无害的伪造节点,而实际处理的却是恶意 Assertion。这是 SAML 特有的攻击面,OIDC 的 JWS 结构不存在此类问题。防御措施:使用经过严格审计的 SAML 库(如 python3-saml、SAML2 Java Toolkit),禁止手写 XML 签名验证逻辑。

NameID 策略:比你想象的麻烦

NameID 是用户在 SP 中的标识符。和 OIDC 的 sub 不一样——sub 在同一个 IdP 下全局唯一——NameID 可以有不同的 Format,而且格式决定了语义:

NameID Format示例值语义
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressuser@company.com邮箱地址
urn:oasis:names:tc:SAML:2.0:nameid-format:persistentJAv2pHy8rNkMp不透明持久标识——同一用户每次登录相同,但不同 SP 间不同(pairwise pseudonymous)
urn:oasis:names:tc:SAML:2.0:nameid-format:transientt+N3cVxKzLpXq临时标识——每次登录都不同(用于隐私场景)
urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified任意字符串由 IdP 和 SP 协商决定
跨 SP 用户关联难题:两个 SP(SP-A 和 SP-B)需要关联同一个用户。如果 IdP 对两个 SP 都使用 persistent 格式,两边的 NameID 值会不同——IdP 为每个 SP 生成不同的 pairwise 标识,无法通过 NameID 直接关联。

解决方案

  • 使用 emailAddress 格式:依赖邮箱格式的唯一性,但改名、离职后重用都会出问题
  • 在 Attributes 中传递通用标识:如 SAML Attribute urn:oid:0.9.2342.19200300.100.1.1(uid),让多个 SP 用同一个属性做关联
  • IdP 端配置固定属性释放策略:在 IdP 配置中为特定 SP 明确指定 NameID 来源字段
工程陷阱:企业客户的 IdP 管理员可以配置 NameID 的 Format 和来源。你会发现客户的 IdP 发来的 NameID 可能不是邮箱,甚至可能是一串你不认识的数字(如 AD 的 objectGUID 的 Base64 编码)。确认"SP 用什么来唯一标识用户"是 SAML 集成的第一件事,也是最容易遗漏的事。

SAML Metadata:互操作性工程的关键

SAML Metadata 是一个 XML 文件,描述了 IdP 或 SP 的技术配置,作用类似于 OIDC 的 Discovery 文档,但 XML 比 JSON 难读得多。

关键 Metadata 片段

Metadata 管理工程要点

  • 证书过期监控:Metadata 中的签名 X.509 证书会过期——到期前 IdP 管理员应更新 Metadata。证书突然过期导致所有 SAML SSO 登录失败是一个经典的 P0 事故。 SP 必须支持 Metadata 的自动刷新(建议每 24 小时拉取一次)或至少提供证书过期的监控和告警。
  • SP 端的 Metadata 配置:确保 Metadata 中的 Location 端点 URL 与 SP 实际使用的 URL 一致。一个常见的配置错误是 SP 使用 https://app.example.com/acs 作为 ACS URL,但 Metadata 发布的却是 https://app.example.com/saml/acs,导致 Recipient 验证失败。
  • 多 IdP 场景:大型 SaaS 产品通常需要支持多个企业客户各自的 IdP(每个客户有自己的 Azure AD、Okta 实例)。需要维护一个 IdP Metadata 注册表,并为每个客户做独立的签名验证。

SAML vs OIDC:全方位对比

维度SAML 2.0OIDC
数据格式XMLJSON
传输方式HTTP-Redirect + HTTP-POST with HTML formHTTP Redirect + POST (JSON)
移动端友好度差(依赖浏览器重定向和表单 POST)好(原生 App 可直接处理)
会话登出SAML Single Logout(复杂,兼容性差)RP-Initiated Logout(简单,广泛支持)
企业存量巨大(ADFS、Shibboleth、老 Okta 部署)快速增长(新系统默认选择)
部署复杂度高(Metadata 交换 + 证书管理 + XML 签名验证)中低(Discovery URL 即开即用)
签名粒度XML 签名(可选择性签名节点)JWS(对整个 Token 签名)
加密能力原生支持 XML Encryption(断言级加密)依赖 JWE(较少使用)
适用场景企业 B2B SSO、政府/金融/医疗行业移动端、B2C、API 认证、现代 Web 应用
工程决策框架:对于 B2B SaaS,你需要同时支持 SAML 和 OIDC。90% 的新客户会选 OIDC,但第一个大客户来的时候很可能要 SAML。尽早实现双协议支持可以避免因"客户只支持 SAML"而被卡在销售流程中。

遗留 SAML 系统迁移策略

迁移的四种策略

策略一:协议桥接(推荐用于无法更换 IdP 的场景)

在 SAML IdP 和现代应用之间插入一个身份代理层(Identity Broker/Gateway),将 SAML 转换为 OIDC:

CODE
遗留 SAML IdP → [SAML-to-OIDC Bridge] → 现代应用(OIDC)

优势:不需要修改 IdP 或应用;劣势:引入了新的组件,增加运维复杂度。

开源方案:Keycloak 可以作为 SAML-to-OIDC 桥接代理,或者使用商业 IDaaS 产品。

策略二:并行运行(风险最低)

新应用部署 OIDC,旧应用保持 SAML,在前端统一路由:

CODE
用户登录 → [统一认证门户]
              ├─ OIDC → 新应用
              └─ SAML → 旧应用

优势:不影响现有系统;劣势:用户需要看到两套登录体验。

策略三:IdP 端双协议(需要 IdP 支持)

如果 IdP 同时支持 SAML 和 OIDC(如 Azure AD、Okta),直接在 IdP 中注册两个应用条目,分别使用不同协议。

优势:应用端不需要处理多协议;劣势:维护两套配置。

策略四:逐步替换(长期方案)

  • 先在新系统中实现 SAML 支持
  • 旧系统下线时迁移到 OIDC
  • 最终关闭 SAML 端点

密码哈希迁移的特殊挑战

用户密码哈希不可逆迁移——SAML 场景下,密码验证在 IdP 侧完成,SP 永远不知道密码本身,因此这方面压力较小。真正的挑战在于:

  • MFA 种子不可迁移:如果用户在 IdP 侧绑定了 TOTP/硬件密钥,迁移到新 IdP 后需要重新注册
  • Session 连续性:迁移期间需要维护新旧 IdP 的并行会话,确保用户无感切换
  • 属性映射:SAML Attributes 和 OIDC Claims 的语义不完全等价,需要映射表

安全分析

已知攻击面

  • XML 签名包装(XSW):前文已述,使用标准化 SAML 库防御
  • Assertion 重放:通过 Assertion ID 去重防御
  • 证书替换攻击:如果 SP 没有正确校验 Metadata 证书指纹,攻击者可以提供自己的证书来伪造 Assertion
  • Golden SAML 攻击:如果 IdP 的签名私钥泄露,攻击者可以为任意用户签发有效的 Assertion——这与 OIDC 的 IdP 私钥泄露是等价的威胁模型
  • 时钟漂移利用:如果 SP 的时钟偏移很大,过大/过小的时间窗口都会被利用——建议 NTP 同步 + 容忍窗口不超过 ±60 秒

与国密算法的结合

在等保/密评场景下,SAML 的 XML 签名可能需要使用 SM2/SM3 国密算法替代 RSA/SHA-256:

  • 国密 XML 签名尚无广泛遵循的国际标准,国内厂商(如吉大正元、北京数字认证)通常采用自定义方案
  • GM/T 0034-2014《基于SM2的证书认证系统密码及安全规范》中的证书格式约束会影响 SAML 证书的互操作性
  • 实践中,部分企业采用"国密 TLS 传输 + 国际算法签名断言"的折中方案——TLS 层使用国密密码套件满足传输加密合规,SAML 断言仍使用 RSA 签名确保互操作性

总结

  • SAML 不会消失:企业 B2B SaaS 必须支持 SAML,这是存量市场的刚性需求
  • 实现双协议:OIDC 是新系统的默认选择,但第一个大客户来的时候很可能要 SAML
  • Metadata 管理是运维重点:证书过期 = P0 事故,必须自动化监控和刷新
  • 不要手写 SAML 解析器:XML 签名验证陷阱多,使用经过审计的成熟库
  • 迁移要渐进:优先在新系统实现 OIDC,旧系统通过 SAML Bridge 逐步过渡

参考来源

相关实践