4. SAML 2.0协议详解:断言、绑定、协议、元数据
说到多云身份管理,SAML 2.0 是个绕不开的话题。我最早接触它是在做企业级 SSO 项目的时候,那时候被各种 XML 搞到头大。但用熟了你会发现,它其实就四个核心概念:断言、绑定、协议、元数据。咱们一个一个拆开聊。
4.1 断言(Assertion)—— 身份的核心凭证
断言说白了就是一张「电子通行证」。IdP(身份提供商)签发它,SP(服务提供商)验证它。我习惯把断言想象成一张盖了公章的介绍信。
断言分三种类型:
- 认证断言:证明用户「是谁」以及「什么时候登录的」
- 属性断言:携带用户的额外信息,比如邮箱、角色、部门
- 授权决策断言:告诉 SP 用户「能不能干某件事」
实际项目中,最常见的是认证断言 + 属性断言组合使用。举个例子:
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_a75adf55-01d7-40cc-929f-dbd8372ebdfc"
IssueInstant="2024-03-15T10:00:00Z">
<saml:Issuer>https://idp.example.com</saml:Issuer>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
zhang.san@example.com
</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"/>
</saml:Subject>
<saml:Conditions NotBefore="2024-03-15T09:55:00Z"
NotOnOrAfter="2024-03-15T10:10:00Z">
<saml:AudienceRestriction>
<saml:Audience>https://sp.example.com</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2024-03-15T09:59:00Z"
SessionIndex="_be9967abd904">
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
关键字段解读:
ID:断言唯一标识,防重放攻击用的IssueInstant:签发时间,我见过很多坑都是时间不同步导致的Conditions:有效期窗口,默认5分钟,别设太长AudienceRestriction:指定目标 SP,防止断言被滥用
我的经验:断言里的 NotOnOrAfter 一定要设。曾经有个客户没设这个字段,结果用户登录一次后,断言被反复用了三个月... 后来我们强制要求所有断言必须有有效期。
4.2 绑定(Binding)—— 怎么传断言
断言造好了,怎么传给 SP?这就是绑定要解决的问题。SAML 2.0 定义了多种绑定方式,但实际生产中常用的就两种:
| 绑定类型 | 传输方式 | 适用场景 |
|---|---|---|
| HTTP Redirect Binding | URL 参数(GET) | SP 发起登录,轻量级场景 |
| HTTP POST Binding | HTML 表单(POST) | IdP 发起登录,断言较大时 |
| Artifact Binding | 先传短令牌,再后台交换 | 高安全场景,避免断言暴露 |
我个人最常用的是 HTTP POST Binding。为什么呢?因为 HTTP Redirect 会把整个断言塞进 URL,一旦断言里属性多了,URL 长度超限就麻烦了。你想想看,一个 Base64 编码的 XML 动不动就几 KB,浏览器能忍,但网关不一定忍得了。
避坑指南:我曾经遇到过一个案例,用 HTTP Redirect Binding 传断言,结果中间有个 WAF 把超长 URL 直接截断了。排查了整整两天才找到原因。从那以后,只要断言超过 2KB,我坚决用 POST Binding。
4.3 协议(Protocol)—— 谁先开口说话
协议定义了 IdP 和 SP 之间的交互流程。说白了就是「谁先发起请求,谁响应」。SAML 2.0 主要有两种模式:
4.3.1 SP 发起的 SSO(SP-Initiated SSO)
这是最常见的场景。用户想访问 SP 的资源,SP 发现没登录,就把用户踢到 IdP 去认证。流程如下:
- 用户访问 SP 受保护资源
- SP 生成
AuthnRequest,重定向到 IdP - IdP 验证用户身份(输入密码、扫码等)
- IdP 生成断言,POST 回 SP
- SP 验证断言,创建本地会话
4.3.2 IdP 发起的 SSO(IdP-Initiated SSO)
用户先登录 IdP 门户,然后点击某个应用图标,IdP 直接推送断言给 SP。这种模式在内部系统里很常见,但安全性稍弱——因为 SP 无法确认用户是不是真的主动发起了请求。
我的建议:对外暴露的系统,尽量用 SP-Initiated SSO。内部管理系统,用 IdP-Initiated 也没问题,但一定要加 InResponseTo 校验,防止断言被重放。
4.4 元数据(Metadata)—— 双方的「身份证」
元数据是 SAML 2.0 里最容易被忽视,但也是最重要的部分。它描述了 IdP 和 SP 的配置信息,包括:
- 实体 ID(Entity ID)
- 支持的绑定类型
- 断言消费地址(ACS URL)
- 签名证书(X.509 Certificate)
- 单点登录/单点登出端点
一个典型的 IdP 元数据长这样:
<EntityDescriptor entityID="https://idp.example.com"
xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>MIID...(Base64 编码的证书)</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://idp.example.com/sso"/>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://idp.example.com/slo"/>
</IDPSSODescriptor>
</EntityDescriptor>
实用技巧:我习惯把元数据文件放在一个公开的 URL 上,比如 https://idp.example.com/metadata。这样 SP 那边配置时直接导入 URL 就行,不用手动复制粘贴。手动复制容易出错,尤其是证书那串 Base64,少一个字符都不行。
4.5 签名与加密 —— 安全不能省
SAML 2.0 的安全机制主要靠两样:签名和加密。签名保证断言没被篡改,加密保证断言内容不被偷看。
实际项目中,我建议:
- 必须签名:断言和
AuthnRequest都要签名。不签名的断言,跟明信片没区别 - 按需加密:如果断言里包含敏感属性(比如身份证号、手机号),就加密。普通角色信息可以不加密,减少性能开销
- 证书轮换:证书有效期到了要换。我见过最惨的案例是证书过期了没人管,整个 SSO 系统瘫痪了一上午
我曾经踩过的坑:有一次配置 SAML 集成,两边都说签名没问题,但就是验证失败。查了半天,发现是 IdP 用了 SHA-1 签名算法,而 SP 只支持 SHA-256。从那以后,我每次对接都会先确认签名算法是否一致。SHA-1 现在基本被弃用了,建议统一用 SHA-256。
4.6 单点登出(SLO)—— 容易被遗忘的角落
很多团队只关注 SSO 登录,却忽略了登出。SAML 2.0 的单点登出有两种方式:
- SP 发起登出:用户在一个 SP 登出,SP 通知 IdP,IdP 再通知其他 SP
- IdP 发起登出:用户在 IdP 门户登出,IdP 广播给所有活跃的 SP
说实话,SLO 的实现比 SSO 复杂得多。因为要保证所有 SP 都收到登出通知,而且不能阻塞。我一般建议:如果系统对安全性要求不是极高,可以不做 SLO,只做局部登出。用户关闭浏览器就完事了。
总结一下:SAML 2.0 的核心就是断言(内容)、绑定(传输)、协议(流程)、元数据(配置)。把这四个概念理清了,多云环境下的身份联合管理就成功了一半。下一章咱们聊聊 OIDC,看看它跟 SAML 2.0 到底有什么区别,什么时候该用谁。