SAML 协议
目录
1. SAML 是什么,解决什么问题
SAML(Security Assertion Markup Language) 是一种基于 XML 的身份联合协议,主要用于在身份提供方(IdP)和服务提供方(SP)之间交换身份信息。
它解决的核心问题是跨系统单点登录(SSO):用户只需在 IdP 完成一次认证,即可访问多个 SP,无需重复登录。典型场景包括企业内部多系统统一登录、跨组织身份联合,以及传统 Web 应用接入集中式身份入口。
SAML、OAuth 2.0、OIDC 三者经常一起被提到,但各自解决的问题不同。SAML 解决跨域身份认证,回答”这个用户是谁”;OAuth 2.0 解决授权委托,回答”这个应用能代表用户访问哪些资源”;OIDC 构建在 OAuth 2.0 之上,为身份认证和用户身份声明提供了标准化层。三者的关系可以概括为:SAML 主要用于身份认证,OAuth 2.0 主要用于授权,OIDC 则是在 OAuth 2.0 之上补充标准化认证能力。
2. SAML 认证流程
SAML 认证主要涉及三个参与方:用户浏览器、身份提供方(IdP)和服务提供方(SP)。认证过程中传递的核心消息对象包括:AuthnRequest(认证请求)、Response(协议响应)、Assertion(身份声明)和 RelayState(登录前上下文)。其中 Assertion 是核心——SP 是否接受登录结果,取决于它是否通过了对 Assertion 的校验。
2.1 SP 发起登录(SP-Initiated SSO)
这是最常见的对接方式:用户先访问业务系统,系统发现用户尚未登录,再跳转到 IdP 完成认证。
对应的处理步骤是:
- 用户访问 SP 的受保护资源。
- SP 发现没有本地会话,生成
AuthnRequest,并通过浏览器重定向到 IdP 的 SSO 端点。 - 浏览器访问 IdP 的 SSO 端点,发起认证流程。
- IdP 完成用户认证,并生成带签名的
SAML Response,或其中包含已签名的Assertion,具体取决于对接配置。 - IdP 通过浏览器返回自动提交表单,其中包含
SAML Response。 - 浏览器将该响应提交到 SP 的 ACS URL。
- SP 验签并校验断言条件,通过后创建本地会话。
SAML 的消息往返通常借助浏览器完成,真正建立信任的是 IdP 与 SP 之间的证书和配置。
2.2 SAML Response 的核心结构
Response 是协议层容器,真正承载身份信息的是 Assertion。
<samlp:Response>
<saml:Issuer>https://idp.example.com/SAML2</saml:Issuer>
<ds:Signature>
<ds:SignatureValue>...</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIICxDCCA...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<saml:Assertion>
<saml:Issuer>https://idp.example.com/SAML2</saml:Issuer>
<saml:Subject>
<saml:NameID>user@example.com</saml:NameID>
</saml:Subject>
<saml:Conditions
NotBefore="2026-03-31T09:00:00Z"
NotOnOrAfter="2026-03-31T09:05:00Z">
<saml:AudienceRestriction>
<saml:Audience>https://sp.example.com/SAML2</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2026-03-31T08:59:30Z"/>
<saml:AttributeStatement>
<saml:Attribute Name="email">
<saml:AttributeValue>user@example.com</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
这个结构里,SP 至少需要关注四类信息:
- 签发方是谁:
Issuer是否与受信任的 IdP 一致。 - 用户是谁:
NameID或属性映射是否满足本地用户识别规则。 - 断言是否仍然有效:
NotBefore、NotOnOrAfter是否通过校验。 - 断言是否发给当前 SP:
Audience是否匹配当前 SP 的Entity ID。
2.3 IdP 发起登录(IdP-Initiated SSO)
有些场景下,用户先进入统一门户(如企业内部导航页),再从门户点击进入某个业务系统。这类流程由 IdP 直接发起,SP 不会预先生成 AuthnRequest。
对应的处理步骤是:
- 用户已在 IdP 完成认证,点击门户中的业务系统入口。
- IdP 直接生成带签名的
SAML Response,或其中包含已签名的Assertion,无需等待 SP 的请求。 - IdP 通过浏览器返回自动提交表单,其中包含
SAML Response。 - 浏览器将该响应以 HTTP POST 的方式提交到 SP 的 ACS URL,通常还会附带
RelayState指示目标页面。 - SP 收到响应后,执行与 SP 发起流程相同的校验:验签、校验
Issuer/Audience/时间窗口等。 - 校验通过后建立本地会话,将用户跳转到
RelayState指定的目标页面。
与 SP 发起方式相比,IdP 发起流程省去了 AuthnRequest 的生成和重定向环节,SP 的处理逻辑更简单,但工程上有几点需要额外关注:
- 通常缺少可用于请求绑定的
InResponseTo:SP 发起方式中,响应里通常会带有可用于关联本次AuthnRequest的InResponseTo信息,SP 可以据此验证响应是否对应当前请求。IdP 发起时通常不存在这类请求绑定信息,SP 无法做同样的请求关联校验,防重放需要依赖其他手段(如断言 ID 去重)。 RelayState需要完整性保护:IdP 发起场景下,RelayState通常承载了目标 URL,如果未做保护,攻击者可以替换它实现跳转篡改。- 深链接支持有限:用户直接访问业务系统某个具体 URL 时,IdP 发起方式无法自然地保留该上下文,需要额外设计目标地址的传递机制。
3. 信任建立:元数据与关键字段
流程能够运行,并不等于 SP 会接受这次登录结果。SP 信任 IdP 发来的响应,前提是双方已经交换元数据,并基于证书建立了信任关系。
3.1 元数据是什么
元数据可以看作 SAML 对接双方的机器可读配置说明书。它描述了一个实体是谁、暴露哪些端点、使用什么证书、支持哪些绑定方式。
对接时,双方通常先交换元数据,再根据元数据完成信任配置:
3.2 IdP 元数据通常包含什么
对 SP 来说,IdP 元数据定义了受信任实体、登录端点和验签证书。
<EntityDescriptor entityID="https://idp.example.com/SAML2">
<IDPSSODescriptor>
<KeyDescriptor use="signing">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIICxDCCA...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
<SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://idp.example.com/SAML2/SSO/Redirect"/>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
</NameIDFormat>
</IDPSSODescriptor>
</EntityDescriptor>
IdP 元数据中的关键内容有:
- 实体标识:用于确认这是哪个 IdP。
- 端点地址:SP 知道该把认证请求发到哪里。
- 签名证书:SP 用它来验证 IdP 发来的响应或断言是否可信。
3.3 SP 元数据通常包含什么
对 IdP 来说,SP 元数据定义了响应投递地址、断言目标和请求签名或断言加密等能力。
<EntityDescriptor entityID="https://sp.example.com/SAML2">
<SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true">
<AssertionConsumerService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://sp.example.com/SAML2/ACS"
index="0"
isDefault="true"/>
<KeyDescriptor use="signing">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIICxDCCA...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIICxDCCA...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
</SPSSODescriptor>
</EntityDescriptor>
工程上需要关注以下配置约束:
- 签名证书和加密证书可以相同,也可以分开配置。
AuthnRequestsSigned="true"表示 SP 发出的认证请求需要签名,IdP 也应具备验签配置。WantAssertionsSigned="true"表示 SP 要求断言必须签名,但前提是 SP 的校验逻辑确实按这个要求执行。
4. 元数据交换方式
前面提到的 Entity ID、SSO URL、ACS URL、签名证书,以及 AuthnRequest 和 Response 依赖的绑定方式,最终都要通过某种方式在 IdP 和 SP 之间交换并保持一致。手工填写参数也能完成对接,但生产环境更适合让这些信息以元数据的形式成组维护,从而减少人工拼装配置带来的错配风险。不同阶段的系统适合不同的交换策略,选择标准通常是风险控制和运维能力。
| 阶段 | 交换方式 | 核心特点 | 适用判断 |
|---|---|---|---|
| POC / 测试 | 手动配置参数 | 先跑通链路,配置直接 | 适合短周期验证和联调,不适合长期维护 |
| 生产首次对接 | 线下交换元数据文件 | 便于审核,边界清晰 | 适合首次建立信任或安全要求较高的场景 |
| 生产稳定运行 | 元数据 URL 自动获取 | 便于同步,利于轮换 | 适合运维流程成熟、需要持续更新的环境 |
4.1 手动配置参数
在联调初期,如果目标是先把登录链路跑通,手动配置参数通常是最快的方式。它本质上是把元数据中的关键项拆开后逐项填写,优点是快,缺点是容易漏项和错配。
通常需要手工确认的参数包括:
Entity IDSSO URL/ACS URL- 签名证书
NameID格式- 用户属性映射规则
适用于功能验证、联调初期和临时测试环境。
这类配置不适合长期用于生产环境,主要问题是:
- 证书更新容易遗漏。
- 端点变更缺少统一同步机制。
- 参数分散在多个配置项中,排查成本高。
4.2 线下交换元数据文件
当系统已经不只是验证“能否登录成功”,而是要正式建立可审核的信任关系时,线下交换元数据文件是生产首次对接中最常见、也最稳妥的方式。双方导出元数据,通过可信渠道交换并人工审核。
IdP 导出元数据 ──► 安全传输 ──► SP 导入并审核
SP 导出元数据 ──► 安全传输 ──► IdP 导入并审核
↓
属性映射确认
↓
联调测试
↓
上线
审核重点包括:
- 证书用途和有效期是否正确。
Entity ID是否唯一且与预期一致。ACS URL、SSO URL是否与实际环境匹配。- 支持的绑定方式是否与双方实现一致。
4.3 元数据 URL 自动获取
系统进入稳定运行阶段后,关注点通常会从“首次对接是否成功”转向“证书轮换和端点变更能否平滑处理”,这时元数据 URL 能显著降低维护成本。
这种方式依赖以下前提:
- 拉取链路本身可信,例如使用受信任的 HTTPS。
- 对元数据内容有校验和审核策略,避免盲目信任远端更新。
- 证书轮换过程支持新旧证书并存的过渡期。
证书轮换通常按以下顺序进行:
- IdP 生成新证书。
- 元数据中同时发布新旧证书。
- SP 拉取更新后,开始同时信任两张证书。
- IdP 切换为新证书签名。
- 观察一段稳定期后,再从元数据中移除旧证书。
这种“重叠信任窗口”是生产环境平滑切换的关键。如果新证书发布后立即替换旧证书,而 SP 尚未完成同步,验签失败会直接影响登录。
5. 安全机制:签名、证书与校验
无论是前面提到的元数据交换、证书轮换,还是 SP 对 Response 和 Assertion 的消费,本质上都建立在同一套底层安全机制之上:数字签名提供来源可信和防篡改保证,证书承载公钥并建立信任锚点,校验逻辑确保接收方只接受合法的消息和断言。
5.1 数字签名的作用与原理
签名在 SAML 中解决两个问题:
- 来源可信:这份消息确实由持有对应私钥的一方签发。
- 内容未被篡改:消息在传输和处理中没有被修改。
数字签名不应简化为“用私钥加密整段数据”。实际过程包括:
- 对待签名内容做规范化处理。
- 计算摘要。
- 使用私钥对摘要相关信息生成签名值。
- 接收方使用公钥验证签名,并对原始内容重新计算摘要比对。
签名方 验证方
│ │
│ 1. 对待签名内容计算摘要 │
│ 2. 使用私钥生成签名值 │
│──────── 消息 + 签名 ───────────────►│
│ │
│ 3. 用公钥验证签名
│ 4. 重新计算摘要
│ 5. 比对摘要是否一致
│ 6. 一致则验签通过
这一机制同时适用于 SAML 消息签名(如签名落在 Response、Assertion,或两者同时签名)和元数据签名(如 IdP/SP 对自身元数据文档签名)。
5.2 证书在 SAML 各环节中的使用
| 场景 | 证书来源 | 作用 |
|---|---|---|
| IdP 对响应或断言签名 | IdP 元数据 | SP 用于验签,确认响应可信 |
| SP 对认证请求签名 | SP 元数据 | IdP 用于验签,确认请求来自受信任的 SP |
| IdP 加密断言 | SP 元数据中的加密证书 | 只有持有对应私钥的 SP 能解密断言 |
| 元数据自身签名 | 签发方的签名证书 | 接收方验证元数据在传输中未被篡改 |
5.3 元数据交换的安全保障
元数据承载了证书和端点等关键信任配置,如果元数据本身在交换过程中被篡改,后续所有 SAML 消息的安全性都会失效。
保障元数据可信的常见手段包括:
- HTTPS 传输:通过受信任的 TLS 链路拉取元数据 URL,防止中间人替换内容。
- 元数据签名:SAML 元数据支持 XML 签名(
<ds:Signature>),接收方在导入前验证签名,确认元数据来自预期实体且未被修改。 - 人工审核:线下交换场景中,接收方应对元数据中的证书指纹、端点地址和
Entity ID逐项核对,确认与预期一致后再导入。 - 变更监控与审计:元数据 URL 自动获取场景下,应记录每次拉取到的元数据变更(如证书更新、端点变化),并在关键变更时触发告警或人工确认。
5.4 SAML 断言的完整校验
工程上常见的问题是:系统只验证签名是否正确,没有把断言约束一起检查完。可靠的校验通常至少包括:
- 验证签名是否来自受信任证书。
- 确认被签名的对象就是当前要消费的
Response或Assertion。 - 验证
Issuer、Audience、Destination、Recipient等约束。 - 验证时间窗口,包括
NotBefore、NotOnOrAfter,并处理合理的时钟偏差。 - 如有
InResponseTo,确认它能对应到本次请求。
签名验证解决可信性,断言条件校验解决有效性与适用范围。 两者缺一不可。
6. 安全最佳实践
SAML 对接能否长期稳定运行,取决于边界条件是否处理完整,不能只看“是否能登录成功”。
6.1 证书与密钥管理
| 实践 | 原因 |
|---|---|
| 定期轮换证书 | 降低长期暴露风险,并为到期前切换预留窗口 |
| 私钥受控存储 | 私钥一旦泄露,攻击者可伪造受信任消息 |
| 区分签名与加密用途 | 有助于职责分离和后续轮换管理 |
| 建立过期告警 | 避免因证书过期导致大面积登录故障 |
6.2 协议与实现安全
| 实践 | 原因 |
|---|---|
| 强制使用 HTTPS | 防止中间人攻击和明文泄露 |
| 要求并验证签名 | 既要配置要求,也要在代码或网关中真正校验 |
| 校验断言时间窗口 | 防止过期断言或超前断言被错误接受 |
校验 Audience 与 Destination |
防止断言被错误投递或跨系统滥用 |
| 处理时钟偏差 | 分布式环境中应允许合理的 clock skew |
对 RelayState 做完整性保护 |
防止被利用进行跳转篡改或状态混淆 |
6.3 常见问题排查
| 问题 | 常见原因 | 排查方向 |
|---|---|---|
| 验签失败 | 证书不匹配、旧证书未下线、签名对象不一致 | 检查元数据、证书用途、签名位置和证书轮换状态 |
Issuer 不匹配 |
Entity ID 配置不一致 |
比对元数据中的 entityID 与消息中的 Issuer |
Audience 校验失败 |
SP 标识配置错误 | 核对 SP 的 Entity ID 与断言中的 Audience |
ACS URL / Destination 错误 |
环境地址变更未同步 | 检查网关、反向代理和元数据中的端点配置 |
| 时间校验失败 | 服务器时钟不同步 | 检查 NTP、时区配置和允许的时钟偏差 |