跳到主要内容
ecdsa.com

SAML vs OIDC:单点登录协议该选哪个

两者都让身份提供商向另一个应用担保用户的身份。SAML 对经浏览器 POST 提交的 XML 断言签名;OpenID Connect 则在 OAuth 2.0 之上对 JSON 令牌签名。

选 SAML 还是 OIDC,是单点登录背后的协议选择。在 SAML 2.0(OASIS 标准,2005年3月)中,身份提供商对一份关于用户的 XML 断言签名,再由浏览器把它带到服务提供商。OpenID Connect(Core 1.0,2014年2月,同时也是 ISO/IEC 26131:2024)在 OAuth 2.0 之上增加了身份层:提供商通常通过授权码流程签发经过签名的 JWT,即 ID 令牌,并且可以在同一次交换中签发供 API 使用的访问令牌。

概览

SAML 2.0OpenID Connect
发布OASIS 标准,2005年3月Core 1.0,2014年2月(2023年勘误);ISO/IEC 26131:2024
令牌XML 断言JWT 格式的 ID 令牌,另有 OAuth 访问令牌和刷新令牌
签名带规范化的 XML 签名,通常为 RSA-SHA256JWS;必须支持 RS256,ES256 等算法也很常见
传输浏览器 POST 或重定向;通过 SOAP 进行工件解析(artifact resolution)通过重定向传递授权码,从令牌端点获取令牌
配置与每个合作方分别交换的 XML 元数据位于 .well-known URL 的发现文档和 JWKS
对 Microsoft Entra ID 的实测结果(见下文)元数据大小为 28~32 KB,含 5 张证书发现文档为 1,728 字节,JWKS 中有 7 个 RSA 密钥
密钥轮换在元数据中发布新证书,往往需要每个合作方手动更新自动:客户端刷新 JWKS 即可
原生应用和移动应用不便(需由浏览器 POST 到服务器)专为此设计(RFC 8252、PKCE)
API 访问不涵盖(需要单独的令牌交换)在同一流程中获得访问令牌
登出通过前端通道或后端通道的单点登出(Single Logout)RP 发起的登出、前端通道登出和后端通道登出(2022年定稿)
典型用户企业 SaaS、高校(eduGAIN)、政府面向消费者的登录、现代 SaaS、移动端、员工身份管理

两者如何让用户登录

在 SAML 中,服务提供商携带 AuthnRequest 把浏览器重定向到身份提供商。用户登录后,IdP 返回一个 HTML 表单,由它 POST 一个 base64 编码的 Response,其中包含断言:颁发者、主体、受众限制、有效期、属性和 XML 签名。服务提供商用 IdP 元数据中的证书验证签名,并检查各项条件。

在 OpenID Connect 中,客户端带着 scope=openid、state、nonce 和 PKCE 挑战重定向到提供商的授权端点。提供商把一次性授权码返回到重定向 URI;客户端再通过直接的 TLS 连接,在令牌端点用它换取 ID 令牌和访问令牌,因此令牌从不经过浏览器的地址栏。

下面的脚本从同一个提供商读取两种配置(2026年10月20日,curl 和 jq 1.8.1)。多次获取时,由于应答的服务器不同,SAML 元数据的大小在 28~32 KB 之间变化,JWKS 中的密钥数量则在 7~8 个之间变化。

对比同一提供商的 OIDC 发现文档与 SAML 元数据
#!/bin/sh
base=https://login.microsoftonline.com/common
echo "== OpenID Connect discovery"
curl -s "$base/v2.0/.well-known/openid-configuration" -o oidc.json
echo "bytes: $(wc -c < oidc.json | tr -d ' ')"
jq -c '{id_token_signing_alg_values_supported, response_types_supported}' oidc.json
curl -s "$(jq -r .jwks_uri oidc.json)" | jq -c '{jwks_keys: (.keys | length), kty: ([.keys[].kty] | unique)}'

echo "== SAML 2.0 metadata"
curl -s "$base/FederationMetadata/2007-06/FederationMetadata.xml" -o saml.xml
echo "bytes: $(wc -c < saml.xml | tr -d ' ')"
grep -o 'SignatureMethod Algorithm="[^"]*"' saml.xml | sort -u
grep -o 'SingleSignOnService Binding="[^"]*"' saml.xml | sort -u
echo "distinct certificates: $(grep -o '<X509Certificate>[^<]*' saml.xml | sort -u | wc -l | tr -d ' ')"
rm -f oidc.json saml.xml
输出
== OpenID Connect discovery
bytes: 1728
{"id_token_signing_alg_values_supported":["RS256"],"response_types_supported":["code","id_token","code id_token","id_token token"]}
{"jwks_keys":7,"kty":["RSA"]}
== SAML 2.0 metadata
bytes: 28179
SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
distinct certificates: 5

依赖方必须检查什么

SAML:用你配置的 IdP 证书验证 XML 签名(切勿使用消息中嵌入的证书),并确认签名覆盖的正是你的代码读取的那个断言;然后检查 Issuer、Destination、InResponseTo、AudienceRestriction、NotBefore 和 NotOnOrAfter,并拒绝重放的消息。难点在于结构本身。XML 签名在规范化之后对按 ID 引用的元素签名,因此一份文档可以同时包含一个签名有效的元素和另一个未签名的元素,而应用实际处理的却是后者。2012年,Somorovsky 等人在 14 个 SAML 框架中的 11 个里演示了这种签名包装;2024年和2025年,ruby-saml(GitLab 使用的库)再次需要修复(CVE-2024-45409、CVE-2025-25291 和 CVE-2025-25292),原因是同一个库中的两个 XML 解析器对同一份文档的解读不一致。

OpenID Connect(Core §3.1.3.7):用提供商 JWKS 中按 kid 匹配到的密钥、以你预期的算法验证 JWS;然后检查 iss、aud(你的客户端 ID)、exp、iat 以及你发送的 nonce。JSON 只有一棵解析树,签名覆盖的正是收到的字节,因此不存在包装问题;剩下的陷阱是接受预期之外的算法、跳过 aud 检查,以及宽松的重定向 URI 匹配,OAuth 安全最佳当前实践(RFC 9700,2025年1月)专门处理了这些问题。OIDC ID 令牌校验器会对照提供商实时的 JWKS 执行这些检查,SAML 查看器则显示签名实际覆盖的是哪个元素。

如何选择

  • 新应用、移动应用、单页应用、API:OpenID Connect,采用授权码流程配合 PKCE。
  • 向企业销售 SaaS:两者都支持;许多客户的身份团队仍然优先配置 SAML。
  • 科研与教育联盟(eduGAIN、InCommon):SAML,配合聚合元数据;OpenID Federation 1.0(2026年2月起成为正式版)正在 eduGAIN 中试点,但尚未成为常态。
  • 政府和受监管行业:遵循你所在生态体系强制要求的协议规范(profile);开放银行及类似体系使用 FAPI 2.0,这是一套经过加固的 OAuth 和 OpenID Connect 规范。
  • 用户和群组的预配(provisioning):两者都不适用;在任一协议之外配合使用 SCIM 2.0(RFC 7643 和 RFC 7644)。

迁移说明

  1. 两者并行运行:在同一个身份提供商中配置 OIDC 应用,并在切换之前把同一个用户标识符(sub 或某个稳定的属性)映射到现有账号。
  2. 显式地把属性映射为声明;SAML 属性名(通常是 URN)很少与 OIDC 声明名一致,而当用户所属的群组很多时,群组声明可能需要单独调用一次 API 才能获取。
  3. 为 OpenID Connect 选用经过认证的库,启用发现机制和 JWKS 缓存(遇到未知的 kid 时刷新),并固定可接受的算法。
  4. 加固仍留在 SAML 上的部分:更新库,要求断言必须签名,拒绝包含多个断言或 DTD 的文档,并监控合作方元数据中证书的过期时间。
  5. 单独规划登出:在必须集中结束会话的场景中,把 SAML 单点登出映射到 OIDC 后端通道登出(Back-Channel Logout)。
  6. 只有在日志显示整整一个业务周期内都没有 SAML 登录之后,才下线 SAML 连接。

参考资料

全部对比