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 去认证。流程如下:

  1. 用户访问 SP 受保护资源
  2. SP 生成 AuthnRequest,重定向到 IdP
  3. IdP 验证用户身份(输入密码、扫码等)
  4. IdP 生成断言,POST 回 SP
  5. 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 到底有什么区别,什么时候该用谁。