前言

本文是 SAML(Security Assertion Markup Language)的概念篇笔记,目标是让对 SAML 完全不了解的读者一次性理解:

  • SAML 是什么、解决什么问题
  • 核心角色和概念
  • 认证流程(SP-Initiated SSO 和 IdP-Initiated SSO)
  • 请求报文(AuthnRequest)和响应报文(Response + Assertion)中各字段的详细含义
  • 绑定方式、元数据、安全机制、单点登出等扩展知识

参考规范:

一、SAML 是什么

1.1 一句话定义

SAML(Security Assertion Markup Language,安全断言标记语言)是一种基于 XML 的开放标准,用于在身份提供方(IdP)和服务提供方(SP)之间交换身份认证与授权数据。

简单说:SAML 就是告诉你”这个用户是谁、他有什么权限、他什么时候登录的”的一套 XML 格式的协议

1.2 诞生的背景

SAML 的历史最早可追溯到 2001 年(SAML 1.0),目前广泛使用的是 SAML 2.0(2005 年发布)

在那个年代:

  • 企业办公场景核心痛点:公司有几十个内部系统(OA、CRM、邮箱、HR 系统等),每个系统都有一套独立的账号密码,员工每天要在不同系统间反复登录。
  • 当时的技术环境:XML 是数据交换的主流标准,JSON 尚未普及;Web Service / SOAP 盛行。

于是 SAML 应运而生,目的就是解决企业跨系统单点登录(SSO)问题——用户只需要在身份中心登录一次,所有接入系统都自动识别其身份。

1.3 核心价值

维度 没有 SAML 有了 SAML
登录方式 每个系统独立登录,N 套账号密码 一次登录,所有系统通用
身份管理 每个系统各自维护用户库 统一身份源(IdP)集中管理
安全管控 密码策略各自为政、无法统一审计 集中认证,可通过多因素认证(MFA)增强安全
新系统接入 新系统从头建用户体系 一键接入现有 IdP,零用户创建成本

1.4 SAML 解决的问题

SAML 主要解决三个问题:

  1. 认证(Authentication):证明”用户是谁”——即用户是否已成功登录。
  2. 属性传递(Attribute):传递用户的详细信息——如邮箱、部门、角色、工号等。
  3. 授权决策(Authorization Decision)(可选):告知 SP 用户是否有权限访问某资源。

对比 OIDC(OIDC 解决的是认证 + 用户信息获取),SAML 天然内置了认证和属性传递,且企业级能力更成熟。

1.5 SAML 与 OAuth2 / OIDC 的关系速览

维度 SAML OIDC OAuth2
数据格式 XML JSON / JWT JSON / 任意
主要用途 认证 + 属性传递 认证 授权
诞生时间 2005(SAML 2.0) 2014 2012
传输方式 HTTP-Redirect / POST / Artifact HTTP + Bearer Token HTTP + Bearer Token
令牌 SAML Assertion(XML) ID Token(JWT) Access Token
移动端支持 ❌ 差(依赖浏览器) ✅ 好 ✅ 好
适用场景 企业 SSO、B2B 联邦、政务金融 现代 Web/移动端 SSO、社交登录 开放平台 API 授权

详细对比可参考之前的文章:OAuth2、OIDC、SAML 协议对比

二、核心角色

SAML 定义了三个核心角色:

2.1 角色总览

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──────────────────┐         ┌──────────────────┐
│ Principal │ │ Identity Provider│
│ (终端用户) │◄───────►│ (IdP) │
└──────────────────┘ │ 身份提供方 │
│ └──────────────────┘
│ │
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Service Provider │◄──信任──│ Identity │
│ (SP) │ │ Provider(IdP)│
│ 服务提供方 │ │ 身份提供方 │
└──────────────────┘ └──────────────────┘

Principal(终端用户)

  • 用户本人,想要访问 SP 上的资源。
  • 与 OIDC 中的 End-User / OAuth2 中的 Resource Owner 对应。

Service Provider(SP,服务提供方)

  • 提供业务功能的应用或系统,如公司内部的 OA 系统、CRM、Jira、Confluence 等。
  • SP 自己不存储用户密码,它信任 IdP 发来的身份声明。
  • 对应 OIDC 中的 Relying Party(RP) / OAuth2 中的 Client

Identity Provider(IdP,身份提供方)

  • 统一管理用户身份的系统,存储用户名、密码、角色、权限等。
  • 负责对用户做认证,并在认证成功后签发 SAML Assertion
  • 常见 IdP 产品:Active Directory Federation Services(AD FS)、Keycloak、Okta、OneLogin、Azure AD、PingFederate 等。
  • 对应 OIDC 中的 OpenID Provider(OP) / OAuth2 中的 Authorization Server

2.2 信任关系(Trust Relationship)

SP 和 IdP 之间需要提前建立信任,方式是通过交换元数据(Metadata)

1
2
SP Metadata ──────► IdP (信任 SP 的签名)
IdP Metadata ──────► SP (信任 IdP 的签名)

双向信任建立后:

  • SP 信任 IdP 签发的 Assertion(因为 SP 持有 IdP 的公钥/证书,可以验签)。
  • IdP 信任 SP 发起的 AuthnRequest(因为 IdP 持有 SP 的公钥,可以验签请求)。

这个信任建立是一次性配置——企业架构师完成一次元数据交换后,所有 SSO 流程的基础就搭建好了。

三、核心概念详解

在深入流程之前,需要先理解 SAML 的几个核心概念。这里采用”概念 + 类比”的方式帮助理解。

核心概念一览

本节共涉及 7 个核心概念,先给出总览表,方便有个整体印象后再逐一展开:

概念 一句话解释 类比 重要性
Assertion(断言) IdP 对用户身份作出的 XML 声明,SAML 的”令牌” 🏢 公司开的”介绍信” ⭐⭐⭐⭐⭐ 核心
Protocol(协议) 定义 SAML 请求-响应对的规范(如 SSO 用的认证请求协议) 📮 信封上的格式规范 ⭐⭐⭐⭐ 重要
Binding(绑定) 规定 SAML 消息如何通过 HTTP 传输(Redirect / POST / Artifact) 🚚 信件的投递方式(平信/挂号/快递) ⭐⭐⭐⭐ 重要
Profile(配置文件) 将角色、协议、绑定组合成完整的交互流程方案 🧾 完整的使用说明书 ⭐⭐⭐ 有用
NameID(用户标识) SAML 中标识用户的唯一字符串,相当于用户的”ID 卡号” 🆔 身份证号码 ⭐⭐⭐⭐⭐ 核心
EntityID(实体标识) SP 或 IdP 的全局唯一标识符(类似 URL) 🏪 店铺的注册编号 ⭐⭐⭐ 有用
元数据(Metadata) XML 文件,描述 SP/IdP 的端点、证书、能力等配置 📋 商家信息登记表(地址、公章等) ⭐⭐⭐ 补充

其中 元数据(Metadata) 虽然同样重要,但由于内容较多且与请求/响应报文配合更紧密,将在第八节单独详述。

SAML 报文全貌(概念在 XML 中的位置)

在逐一展开前,先看一份完整的 SAML 报文结构关系图,清晰展示每个概念在 XML 报文中的具体位置和层级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌──────────────────────────────────────────────────────────────┐
│ SAML 协议消息结构总览 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ◆ 请求报文(SP → IdP):AuthnRequest │
│ ├─ <Issuer> ← EntityID 概念 (SP 的身份) │
│ ├─ <Signature> ← 安全机制概念 │
│ ├─ <NameIDPolicy> ← NameID 概念 (期望的格式) │
│ ├─ <RequestedAuthnContext> ← 认证强度概念 │
│ └─ <Conditions> ← 条件限制概念 │
│ │
│ ◆ 响应报文(IdP → SP):Response │
│ ├─ <Issuer> ← EntityID 概念 (IdP 的身份) │
│ ├─ <Status> ← Protocol 概念 (处理结果) │
│ └─ <Assertion> ← ⭐ **Assertion 概念** (核心令牌) │
│ ├─ <Issuer> ← EntityID 概念 │
│ ├─ <Signature> ← 安全机制概念 │
│ ├─ <Subject> ← 用户身份 │
│ │ └─ <NameID> ← ⭐ **NameID 概念** │
│ ├─ <Conditions> ← 条件限制 (有效期 / Audience) │
│ ├─ <AuthnStatement> ← 认证声明 (认证时间/方式) │
│ └─ <AttributeStatement> ← 属性声明 (用户属性) │
│ │
│ ◆ 传输方式:Binding 概念 │
│ ├─ HTTP-Redirect: URL Query 参数传 AuthnRequest │
│ └─ HTTP-POST: HTML 表单自动提交传 Response │
│ │
│ ◆ 完整流程定义:Profile 概念 │
│ └─ Web Browser SSO Profile 串联上述所有元素 │
│ │
└──────────────────────────────────────────────────────────────┘

下面逐一展开每个概念,每个概念都会给出其在 XML 报文中的完整结构

3.1 Assertion(断言)

**Assertion 是 SAML 的”令牌”**,是 IdP 对用户身份所作出的声明——本质是一段 XML。

Assertion 在 XML 中的完整结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="a-unique-identifier" <!-- 断言唯一 ID -->
IssueInstant="2026-05-10T08:30:05Z" <!-- 签发时间(UTC) -->
Version="2.0"> <!-- SAML 版本 -->

<saml:Issuer>https://idp.company.com/adfs/services/trust</saml:Issuer>

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<!-- 数字签名:确保 Assertion 的完整性和来源可信性 -->
</Signature>

<saml:Subject> <!-- ★ 用户身份 -->
<saml:NameID ...>...</saml:NameID> <!-- 用户唯一标识 -->
<saml:SubjectConfirmation ...>...</saml:SubjectConfirmation> <!-- 确认机制 -->
</saml:Subject>

<saml:Conditions <!--使用条件 -->
NotBefore="..." NotOnOrAfter="...">
<saml:AudienceRestriction>
<saml:Audience>...</saml:Audience> <!-- 限定只能由谁使用 -->
</saml:AudienceRestriction>
</saml:Conditions>

<!-- 三类声明(至少包含一种)-->
<saml:AuthnStatement .../> <!-- ① 认证声明 -->
<saml:AttributeStatement .../> <!-- ② 属性声明 -->
<!-- <saml:AuthzDecisionStatement .../> --> <!-- ③ 授权决策声明(可选)-->

</saml:Assertion>

一个 Assertion 可以包含以下三类声明(至少包含一种):

① Authentication Statement(认证声明)

证明”用户在什么时间、通过什么方式完成了认证”。

1
2
3
4
5
<saml:AuthnStatement AuthnInstant="2026-05-10T08:30:00Z" SessionIndex="abc123">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>

② Attribute Statement(属性声明)

传递用户的详细信息/属性。

1
2
3
4
5
6
7
8
9
10
11
<saml:AttributeStatement>
<saml:Attribute Name="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue>zhangsan@company.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="department" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue>Engineering</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue>admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>

③ Authorization Decision Statement(授权决策声明)

告诉 SP 用户是否有权执行某个操作或访问某个资源(SAML 2.0 中较少使用,大多数场景靠 SP 自己鉴权)。

类比理解:可以把 SAML Assertion 想象成”一张盖了章的介绍信”。公司(IdP)为你出具介绍信,上面写着”此人身份已验证”(认证)、”他是工程部张三”(属性)、”他可以进入大楼三层”(授权)。你拿着这张信,大楼保安(SP)看到信上的公章(签名)是真的,就放你进去了。

3.2 Protocol(协议)

SAML 定义了一系列请求-响应对(Request-Response Pairs),称为 SAML Protocol。最常用的有:

协议 请求元素 响应元素 用途
Authentication Request Protocol <AuthnRequest> <Response> 单点登录(SSO) ——这是最核心的协议
Single Logout Protocol <LogoutRequest> <LogoutResponse> 单点登出(SLO)
Artifact Resolution Protocol <ArtifactResolve> <ArtifactResponse> 通过 Artifact 间接获取 Assertion
Name Identifier Management Protocol <ManageNameIDRequest> <ManageNameIDResponse> 管理用户标识映射
Assertion ID Request Protocol 无 Request <AssertionIDResponse> 通过 ID 直接查询 Assertion

本文聚焦 Authentication Request Protocol(登录)和 Single Logout Protocol(登出)。

Protocol 在 XML 中的体现

Protocol 定义了 SAML 消息的最外层请求-响应包裹元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- 🔵 请求协议骨架:<samlp:AuthnRequest> -->
<samlp:AuthnRequest
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="a1b2c3d4-..." <!-- 请求唯一 ID -->
Version="2.0" <!-- SAML 版本 -->
IssueInstant="2026-05-10T08:30:00Z" <!-- 请求签发时间 -->
Destination="https://idp.company.com/sso/post"> <!-- 目标 IdP 端点 -->
<saml:Issuer>https://sp.company.com/saml/metadata</saml:Issuer>
<saml:Subject>...</saml:Subject>
<saml:NameIDPolicy .../>
<saml:Conditions .../>
<saml:RequestedAuthnContext .../>
</samlp:AuthnRequest>

<!-- 🟢 响应协议骨架:<samlp:Response> -->
<samlp:Response
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="r1234567-..." <!-- 响应唯一 ID -->
Version="2.0"
IssueInstant="2026-05-10T08:30:05Z"
InResponseTo="a1b2c3d4-..."> <!-- 关联的请求 ID -->
<saml:Issuer>https://idp.company.com/adfs/services/trust</saml:Issuer>
<samlp:Status> <!-- ★ 协议处理状态 -->
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion>...</saml:Assertion> <!-- 0 或 1 个 Assertion -->
</samlp:Response>

命名空间约定

  • samlp: = urn:oasis:names:tc:SAML:2.0:protocol — 协议层元素(请求/响应/状态码)
  • saml: = urn:oasis:names:tc:SAML:2.0:assertion — 断言层元素(Assertion/Issuer/Subject/Conditions 等)

这是 SAML 2.0 的标准命名空间划分,所有实现都遵循此约定。

3.3 Binding(绑定方式)

Binding 规定了 SAML 协议消息(AuthnRequest、Response 等)应该如何通过 HTTP 传输。

常见的 Binding 有四种:

Binding 传输方式 请求方向 适用场景
HTTP-Redirect URL Query 参数(Base64 + 签名) SP → IdP SP 发起 AuthnRequest,轻量、适合 GET 重定向
HTTP-POST HTML 表单自动提交(POST Body) IdP → SP 或 SP → IdP 传输带签名的 Assertion(XML 可能很大),最常用
HTTP-Artifact 传一个短码(Artifact),SP 再后端直连取 Assertion IdP → SP 超长报文或有防中间人需求时
SOAP SOAP 协议(SOAP over HTTP) SP ↔ IdP 服务端直连 单点登出、Artifact 解析等服务端场景

实际中最常见的组合是:

  • SP → IdP:通过 HTTP-Redirect Binding 发送 AuthnRequest。
  • IdP → SP:通过 HTTP-POST Binding 返回 SAML Response(含 Assertion)。

Binding 在 HTTP 层面的体现

HTTP-Redirect Binding(SP → IdP 传递 AuthnRequest):

1
2
3
4
5
6
7
GET /sso/redirect?
SAMLRequest=nZJNj9swDIZ%2F... ← Deflate 压缩 + Base64 编码的 AuthnRequest XML
&RelayState=https%3A%2F%2Fsp.company.com%2Fdashboard ← 回跳地址(URL 编码)
&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256 ← 签名算法
&Signature=base64... ← SP 私钥签名(防篡改)
HTTP/1.1
Host: idp.company.com

HTTP-POST Binding(IdP → SP 传递 Response):

1
2
3
4
5
6
<!-- IdP 返回给浏览器的 HTML,页面加载后自动提交 -->
<form method="POST" action="https://sp.company.com/saml/acs">
<input type="hidden" name="SAMLResponse" value="PD94bWwgdmVyc2lvbj0iMS4wIj8+..." />
<input type="hidden" name="RelayState" value="https://sp.company.com/dashboard" />
</form>
<script>document.forms[0].submit();</script>

HTTP-Artifact Binding(IdP → SP 先传短码,SP 再回连取 Assertion):

1
2
3
4
5
6
7
8
9
10
11
12
# ① 浏览器收到的是短 Artifact 码
<form method="POST" action="https://sp.company.com/saml/acs">
<input type="hidden" name="SAMLart" value="AAQAADWNe9Pv0C6GXh8TqP0HkB8fGjFkIhJm" />
</form>

# ② SP 通过 SOAP 回连 IdP 真正取 Assertion
POST /artifac/resolve HTTP/1.1
<soap:Envelope>
<samlp:ArtifactResolve>
<samlp:Artifact>AAQAADWNe9Pv0C6GXh8TqP0HkB8fGjFkIhJm</samlp:Artifact>
</samlp:ArtifactResolve>
</soap:Envelope>

Binding 参数速查表

HTTP 参数 所属 Binding 内容 处理方式
SAMLRequest HTTP-Redirect AuthnRequest XML Deflate 压缩 → Base64 → URL 编码
SAMLResponse HTTP-POST Response XML Base64 编码(不压缩)
SAMLart HTTP-Artifact Artifact 短码 固定长度字节码
RelayState Redirect / POST 回跳 URL 明文或 URL 编码
SigAlg + Signature HTTP-Redirect 数字签名 仅 Redirect Binding 需在 URL 上签名

3.4 Profile(配置文件/流程模式)

Profile 定义了 SAML 在特定场景下的完整交互流程,它将角色、协议、绑定组合在一起形成可操作的方案。

SAML 2.0 定义了多种 Profile,最常见的是:

Profile 描述
Web Browser SSO Profile 最核心的配置——用浏览器做单点登录
Single Logout Profile 一次性登出所有已登录的应用
Artifact Resolution Profile 通过 Artifact 间接获取 Assertion
Name Identifier Mapping Profile 跨域时的用户标识映射
Assertion Query/Request Profile SP 主动查询用户 Assertion

日常开发中遇到的”SAML SSO“几乎都是指 Web Browser SSO Profile

3.5 NameID(用户标识)

NameID 是 SAML 中标识用户的唯一字符串,相当于 OIDC 中的 sub(Subject)。

NameID 在 XML 中的完整结构

NameID 作为 <Subject> 的子元素出现,其完整上下文如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<saml:Subject>
<saml:NameID
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" <!-- NameID 格式 -->
NameQualifier="https://idp.company.com/adfs/services/trust" <!-- IdP 限定名 -->
SPNameQualifier="https://sp.company.com/saml/metadata"> <!-- SP 限定名 -->
8f7e6d5c-4b3a-2910-fedc-ba9876543210 <!-- 用户唯一标识值 -->
</saml:NameID>

<!-- SubjectConfirmation:确认机制,防止 Assertion 被滥用 -->
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
NotOnOrAfter="2026-05-10T08:40:05Z"
Recipient="https://sp.company.com/saml/acs"
InResponseTo="a1b2c3d4-..."/>
</saml:SubjectConfirmation>
</saml:Subject>

NameID 元素各属性

属性 含义
Format NameID 的格式类型(参见下方表格)
NameQualifier 限定该 NameID 的 IdP 的 EntityID(可选)
SPNameQualifier 限定该 NameID 有效的 SP 的 EntityID(可选)
元素文本值 用户的唯一标识字符串

简洁用法(省去限定名):

1
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">zhangsan@company.com</saml:NameID>

NameID 的常见格式(Format):

Format URI 示例值 说明
urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified u12345 未指定格式,由 IdP 自定义
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress user@company.com 邮箱格式
urn:oasis:names:tc:SAML:2.0:nameid-format:persistent abc-def-ghi-jkl 持久的匿名标识(最推荐,企业场景默认
urn:oasis:names:tc:SAML:2.0:nameid-format:transient session-temp-xyz 临时标识(每次登录不同,仅用于单次会话)
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName CN=张三,OU=Engineering,O=Company X.509 证书主题名
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName COMPANY\zhangsan Windows 域用户

重要概念——NameID 持久性

  • persistent:用户在 IdP 和 SP 之间的标识是固定的、持久的(不随会话改变)。即使清除了会话,下次登录同一个 SP,NameID 不变。这是推荐的企业场景选择
  • **transient**:每次 SSO 登录得到不同的 NameID,只对当前会话有意义,不需要跨会话保留。
  • **emailAddress**:直接用邮箱做标识,简单但存在用户更换邮箱后无法关联的风险。

3.6 EntityID(实体标识)

EntityID 是 SP 或 IdP 的全局唯一标识——通常是一个 URL(不一定要可访问,只是一个标识符)。

1
2
SP 的 EntityID:  https://myapp.company.com/saml/metadata
IdP 的 EntityID: https://idp.company.com/adfs/services/trust

EntityID 在 XML 中的体现

EntityID 在 SAML 报文中的多个位置出现,下面是其出现的所有位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- ① AuthnRequest 中的 Issuer:标识请求发起者(SP) -->
<samlp:AuthnRequest ...>
<saml:Issuer>https://sp.company.com/saml/metadata</saml:Issuer> ← SP 的 EntityID
</samlp:AuthnRequest>

<!-- ② Response 中的 Issuer:标识响应签发者(IdP) -->
<samlp:Response ...>
<saml:Issuer>https://idp.company.com/adfs/services/trust</saml:Issuer> ← IdP 的 EntityID
<saml:Assertion ...>

<!-- ③ Assertion 中的 Issuer(与 Response 一致,也是 IdP) -->
<saml:Issuer>https://idp.company.com/adfs/services/trust</saml:Issuer> ← IdP 的 EntityID

<!-- ④ AudienceRestriction 中的 Audience:限定目标 SP -->
<saml:Conditions>
<saml:AudienceRestriction>
<saml:Audience>https://sp.company.com/saml/metadata</saml:Audience> ← SP 的 EntityID
</saml:AudienceRestriction>
</saml:Conditions>
</saml:Assertion>
</samlp:Response>

<!-- ⑤ LogoutRequest 中的 Issuer:同样是 SP 的 EntityID -->
<samlp:LogoutRequest ...>
<saml:Issuer>https://sp.company.com/saml/metadata</saml:Issuer>
</samlp:LogoutRequest>

关键校验规则

  • SP 在验签时,必须校验 Response/IssuerAssertion/Issuer 是否等于信任的 IdP 的 EntityID
  • SP 在校验 AudienceRestriction 时,必须检查 <Audience> 中是否包含自己的 EntityID
  • AuthnRequest 中的 <Issuer> 必须与 SP 元数据中配置的 EntityID 一致,IdP 据此查找 SP 的配置。

EntityID 在 SAML 中的作用相当于”身份证号”:

3.7 概念关系总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
graph TB
A[SAML] --> B[角色]
A --> C[断言 Assertion]
A --> D[协议 Protocol]
A --> E[绑定 Binding]
A --> F[配置文件 Profile]
B --> G[Principal 用户]
B --> H[SP 服务提供方]
B --> I[IdP 身份提供方]
C --> J[认证声明 AuthnStatement]
C --> K[属性声明 AttributeStatement]
C --> L[授权决策声明 AuthzDecisionStatement]
D --> M[认证请求协议]
D --> N[单点登出协议]
E --> O[HTTP-Redirect]
E --> P[HTTP-POST]
E --> Q[HTTP-Artifact]
F --> R[Web Browser SSO Profile]
F --> S[Single Logout Profile]

四、SAML SSO 认证流程

SAML 2.0 定义了两种典型的 SSO 流程:

4.1 SP-Initiated SSO(SP 发起登录)⭐ 最常用

场景:用户尝试访问一个 SP 应用(如 Jira),但尚未登录——SP 把用户踢到 IdP 登录,验证通过后跳转回来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sequenceDiagram
participant User as 用户(Principal)
participant SP as Service Provider(Jira,SP)
participant IdP as Identity Provider(公司统一认证中心)

User->>SP: 1. 访问 https://jira.company.com
Note over SP: 2. 检测到未登录,生成 AuthnRequest
SP->>User: 3. 302 重定向到 IdP(携带 SAMLRequest)
Note over User: 浏览器 URL: https://idp.company.com/SSO?SAMLRequest=base64...
User->>IdP: 4. 浏览器到达 IdP 登录页面
Note over IdP: 5. 用户填写账号密码(或使用 Cookie 的已有会话)
IdP->>User: 6. IdP 生成包含 Assertion 的 Response
Note over User: HTML 表单自动 POST: <form action="https://jira.company.com/saml/acs" method="POST">
User->>SP: 7. 浏览器自动 POST SAML Response 到 SP 的 ACS URL
Note over SP: 8. SP 验签、校验 Assertion,建立本地会话
SP->>User: 9. 返回业务页面,用户已登录

流程详解(对应上述编号)

  1. 用户访问 SP:用户在浏览器中输入 https://jira.company.com
  2. SP 检测未登录:SP 发现用户没有有效的本地会话,于是决定向 IdP 发起 SAML 认证请求。
  3. SP 重定向用户到 IdP:SP 构造一个 <AuthnRequest> XML,Base64 编码后通过 HTTP-Redirect Binding 让浏览器跳转到 IdP 的 SSO 端点。
    • 浏览器地址变成:https://idp.company.com/SSO?SAMLRequest=xxxx&SigAlg=yyyy&Signature=zzzz
  4. 浏览器到达 IdP 登录页:IdP 解析 AuthnRequest,知道是哪个 SP 发起的请求,展示登录页面。
  5. 用户认证:用户输入账号密码(如果 IdP 已记住该用户的会话,此步可能跳过——这就是 SSO 的无缝体验)。
  6. IdP 生成响应:IdP 验证用户凭证后,生成包含 <Assertion><Response>,用 IdP 的私钥签名,然后通过 HTTP-POST Binding 返回给浏览器——一个包含 SAMLResponse 的自动提交 HTML 表单。
  7. 浏览器提交到 SP:浏览器自动将表单 POST 到 SP 的 ACS(Assertion Consumer Service)URL
  8. SP 验签并建立会话:SP 接收到 SAML Response,使用 IdP 的公钥验证 XML 签名,校验 Assertion 中的各种条件(时间、Audience 等),确认无误后获取用户的 NameID 和属性,建立本地会话(如写入 Session Cookie)。
  9. 返回业务页面:用户已登录,SP 展示正常业务内容。

SSO 的精髓在第 5 步:如果用户已经在 IdP 有有效会话(如刚刚登录过其他系统),IdP 会自动签发 Assertion,用户无感知地完成所有 SP 的登录,这就是”一次登录,处处可用”。

4.2 IdP-Initiated SSO(IdP 发起登录)

场景:用户首先登录 IdP 的门户页面/应用列表页(如 Office 365 的 App Launcher),点击某个应用的图标,跳转到该应用(无需再登录)。

1
2
3
4
5
6
7
8
9
10
11
12
sequenceDiagram
participant User as 用户
participant IdP as Identity Provider(门户)
participant SP as Service Provider(应用)

User->>IdP: 1. 登录 IdP 门户(https://portal.company.com)
Note over IdP: 2. 用户点击"Jira"应用图标
IdP->>User: 3. 构造 SAML Response,通过 HTTP-POST 发送到 SP ACS URL
Note over User: 自动 POST: SAMLResponse=base64...
User->>SP: 4. 浏览器 POST SAMLResponse 到 SP
Note over SP: 5. SP 验签,建立本地会话
SP->>User: 6. 返回业务页面

IdP-Initiated 与 SP-Initiated 的区别

对比维度 SP-Initiated IdP-Initiated
谁先发起 SP(用户点击了 SP 的链接) IdP(用户从 IdP 门户点击应用图标)
有 AuthnRequest 吗 ✅ 有 没有(IdP 直接返回 Response)
有无 RelayState ✅ 有(跳回原页面) ✅ 有(指定目标页面)
安全性 更高(有请求-响应配对,IdP 可验证 request) 较低(无请求关联,IdP 单方面发 Assertion)
典型场景 用户直接访问业务系统被重定向 从公司门户/应用商店跳转到应用

4.3 RelayState 的作用

无论 SP-Initiated 还是 IdP-Initiated,流程中经常会出现一个叫 RelayState 的参数。

  • SP-Initiated:RelayState 由 SP 在 AuthnRequest 中附带,告诉 IdP 认证成功后应该回到 SP 的哪个页面。例如用户访问 https://jira.company.com/projects/ABC 被重定向,RelayState 就是 https://jira.company.com/projects/ABC
  • IdP-Initiated:IdP 自己的门户点击某个应用时,RelayState 告诉 SP 需要跳转到应用的哪个具体页面。
1
2
3
4
5
6
<!-- 通过 HTTP-POST 传递 RelayState -->
<form method="post" action="https://sp.company.com/saml/acs">
<input type="hidden" name="SAMLResponse" value="base64-encoded-xml" />
<input type="hidden" name="RelayState" value="https://sp.company.com/dashboard" />
<!-- ... -->
</form>

提示:由于 RelayState 在请求/响应中明文传递,SP 应在校验 Assertion 后验证 RelayState 是否合法,防止被篡改劫持到恶意页面。

五、SAML 请求报文详解(AuthnRequest)

当 SP 需要让用户去 IdP 认证时,SP 构造一个 <AuthnRequest>。本节以 HTTP-Redirect Binding 为例,拆解其所有参数。

5.1 AuthnRequest 概览

1
2
3
4
5
6
7
8
9
10
11
┌─────────────────────────────────────────────────┐
│ AuthnRequest │
│ ├─ Issuer(SP 的 EntityID,谁发起的请求) │
│ ├─ Signature(SP 的签名,证明请求来自合法 SP) │
│ ├─ Subject(可选,指定特定用户) │
│ ├─ NameIDPolicy(期望的 NameID 格式) │
│ ├─ Conditions(请求有效期等约束) │
│ ├─ RequestedAuthnContext(期望的认证强度) │
│ ├─ Scoping(指定 IdP 代理链等) │
│ └─ Extensions(扩展字段) │
└─────────────────────────────────────────────────┘

5.2 完整示例(附带每行注释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<samlp:AuthnRequest
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="a1b2c3d4-e5f6-7890-abcd-ef1234567890" <!-- 请求的唯一标识用于防重放和跟踪 -->
Version="2.0" <!-- SAML 版本,固定为 2.0 -->
IssueInstant="2026-05-10T08:30:00Z" <!-- 请求签发时间(UTC),IdP 会校验与当前时间偏差是否在允许范围内 -->
Destination="https://idp.company.com/sso/post" <!-- IdP 的 SSO 端点 URL,IdP 应校验此字段是否与自身端点匹配 -->
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" <!-- 期望 IdP 使用什么 Binding 返回响应 -->
AssertionConsumerServiceURL="https://sp.company.com/saml/acs" <!-- SP 的 ACS URL,IdP 应把 Response POST 到这个地址 -->
ForceAuthn="false" <!-- 是否强制用户重新认证(即使已有 IdP 会话)。true=强制重新登录 -->
IsPassive="false"> <!-- 是否只做静默认证(用户不可见交互)。true=不展示登录页,无会话则返回错误 -->

<!-- Issuer:请求发起者(SP)的 EntityID,IdP 通过此字段识别是哪个 SP 发起的请求 -->
<saml:Issuer>https://sp.company.com/saml/metadata</saml:Issuer>

<!-- Signature:SP 的数字签名,确保请求的完整性和来源可信性 -->
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="#a1b2c3d4-e5f6-7890-abcd-ef1234567890">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>base64-encoded-hash</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>base64-encoded-signature-value</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>base64-encoded-sp-certificate</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>

<!-- Subject:指定特定用户(很少用,一般 SSO 由当前登录用户决定) -->
<!-- <saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">zhangsan@company.com</saml:NameID>
</saml:Subject> -->

<!-- NameIDPolicy:SP 期望 IdP 在 Assertion 中使用什么格式的 NameID -->
<samlp:NameIDPolicy
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" <!-- 推荐的持久化标识格式 -->
AllowCreate="true" <!-- 如果 IdP 中没有此用户,是否允许 IdP 创建新的临时标识(不影响实际用户创建) -->
SPNameQualifier="https://sp.company.com/saml/metadata"/> <!-- SP 的限定名(通常等于 Issuer) -->

<!-- RequestedAuthnContext:SP 要求的认证上下文/认证强度 -->
<samlp:RequestedAuthnContext Comparison="exact">
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:X509
</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>

<!-- Conditions:请求的有效期等限制(通常与 SP 元数据中的配置一致) -->
<saml:Conditions
NotBefore="2026-05-10T08:29:00Z" <!-- 请求有效期的开始时间 -->
NotOnOrAfter="2026-05-10T08:31:00Z" /> <!-- 请求有效期的结束时间(通常只给很短的窗口,如 ±1~3 分钟) -->

</samlp:AuthnRequest>

5.3 AuthnRequest 属性详解

顶层属性

属性 必选? 含义 说明
ID ✅ 是 请求唯一标识 UUID 格式(如 _a1b2c3d4-e5f6...)。IdP 可用于防重放日志跟踪。有些实现以”_“开头
Version ✅ 是 SAML 版本号 固定为 2.0
IssueInstant ✅ 是 请求签发时间(UTC) 格式为 YYYY-MM-DDTHH:MM:SSZ。IdP 用于判断请求是否在许可的时间窗口内
Destination ✅ 是 目标 IdP SSO 端点 IdP 应校验此 URL 是否与自己配置的 SSO 端点一致,防止请求被重定向到恶意地址
ProtocolBinding ❌ 否 期望的响应 Binding 告诉 IdP:你应该用哪种 Binding 返回 Response。可选值见下方
AssertionConsumerServiceURL ❌ 否 SP 的 ACS URL IdP 将 Response POST 到这个地址。在 SP 元数据中提前配置过,此处可省略,但显式指定更明确
AssertionConsumerServiceIndex ❌ 否 ACS URL 索引 如果 SP 元数据中定义了多个 ACS URL,可以用索引值引用其中一个。与 AssertionConsumerServiceURL 互斥
AttributeConsumingServiceIndex ❌ 否 属性消费服务的索引 引用 SP 元数据中定义的属性需求配置,指示 IdP 需要返回哪些用户属性
ForceAuthn ❌ 否 是否强制重新认证 false(默认):如果用户已有 IdP 会话,直接签发 Assertion。true:即使用户有会话,也必须重新输入密码(如财务系统敏感操作)
IsPassive ❌ 否 是否静默认证 false(默认):正常显示登录页。true:IdP 不得与用户交互,如果无有效会话则返回错误(用于后台静默检查登录状态)
ProviderName ❌ 否 服务提供方名称 可读的 SP 名称,IdP 登录页可展示给用户看(如 “你正在登录 Jira”)

ProtocolBinding 可选值

URI 含义
urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST IdP 通过 HTTP-POST 返回 Response(最常用)
urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact IdP 通过 Artifact Binding 返回 Response
urn:oasis:names:tc:SAML:2.0:bindings:PAOS 用于 ECP Profile(扩展客户端代理)

子元素详解

子元素 必选? 说明
<Issuer> ✅ 是 SP 的 EntityID,IdP 据此查找该 SP 的元数据和配置
<Signature> ❌ 否 数字签名。HTTP-Redirect Binding 一般使用 URL 参数签名(而非 XML 签名),HTTP-POST Binding 中可能包含 XML 签名
<Subject> ❌ 否 指定某个特定用户。少用——一般 SSO 是让”当前操作的用户”去登录,而不是预先指定一个用户
<NameIDPolicy> ❌ 否 指示 IdP 使用什么格式的 NameID(见上方 3.5 节的 NameID 格式表)
<Conditions> ❌ 否 请求有效期限制。通常只给一个很小的窗口(如 ±3 分钟),防止请求被截获后重放
<RequestedAuthnContext> ❌ 否 要求 IdP 必须使用某种级别的认证方式——例如不能只用简单密码,必须使用 MFA
<Scoping> ❌ 否 用于 IdP 代理/链式认证场景,指定 IdP 可以向上游代理请求。企业内部场景很少用
<Extensions> ❌ 否 扩展字段,用于厂商自定义参数。SAML 规范允许扩展,但不是标准

5.4 RequestedAuthnContext(认证强度要求)

AuthnContextClassRef 预定义的认证上下文 URI:

URI 含义
urn:oasis:names:tc:SAML:2.0:ac:classes:Password 简单的密码认证
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 密码 + HTTPS 传输保护(最常见)
urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract 手机双因素
urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard 智能卡认证
urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos Kerberos 认证
urn:oasis:names:tc:SAML:2.0:ac:classes:X509 证书认证
urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession 基于之前的会话(用户已登录过)

Comparison 属性的可选值:

含义
exact 精确匹配——IdP 必须使用列表中的某个认证上下文
minimum 最低要求——IdP 使用的认证上下文安全级别至少列表中的最低值
maximum 最高限制——IdP 使用的认证上下文安全级别不超过列表中的最高值
better 高于要求——IdP 使用的认证上下文安全级别应高于列表中所有值

5.5 通过 HTTP-Redirect Binding 传输 AuthnRequest

当使用 HTTP-Redirect Binding 时,AuthnRequest 以 URL 参数形式传递,而不是放入 URL Body

URL 参数

1
2
3
4
5
https://idp.company.com/sso/redirect?
SAMLRequest=base64-encoded-and-defalted-authnrequest ← AuthnRequest 先 deflate 压缩,再 Base64 编码
&RelayState=https://sp.company.com/dashboard ← 登录成功后要跳转回的页面
&SigAlg=http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 ← 签名算法
&Signature=base64-encoded-signature ← SP 对 SAMLRequest + RelayState 的签名
参数 描述
SAMLRequest 经过 deflate 压缩 + Base64 编码 的 AuthnRequest XML
RelayState 回跳地址,SP 认证成功后返回的页面 URL
SigAlg 签名算法 URI
Signature SP 使用自己的私钥对 SAMLRequest+RelayState 的签名值,IdP 用 SP 公钥验签

注意:HTTP-Redirect 使用 URL 查询参数签名,而非 XML 数字签名。因为 URL 长度有限,SP 对 AuthnRequest 先进行 deflate 压缩再 Base64 编码。IdP 收到后先 Base64 解码再 inflate 解压还原 XML。

六、SAML 响应报文详解(Response + Assertion)

当 IdP 成功认证用户后,构造一个 <Response> 返回给 SP。这是 SAML 中最核心、最复杂的部分。

6.1 Response 概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────────┐
│ Response(SAML 协议响应) │
│ ├─ Issuer(IdP 的 EntityID) │
│ ├─ Signature(IdP 的签名,保证完整性和来源) │
│ ├─ Status(认证结果:成功/失败/错误) │
│ └─ Assertion(断言,审计真正的令牌) │
│ ├─ Issuer(IdP 的 EntityID) │
│ ├─ Signature(对 Assertion 的独立签名) │
│ ├─ Subject(用户身份 NameID) │
│ ├─ Conditions(断言有效期和限制) │
│ ├─ Advice(可选,额外建议信息) │
│ ├─ AuthnStatement(认证声明) │
│ ├─ AttributeStatement(属性声明) │
│ └─ AuthzDecisionStatement(授权声明,可选) │
└─────────────────────────────────────────────────┘

6.2 完整示例(附带每行注释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="r1234567-890a-bcde-f012-34567890abcd" <!-- 响应的唯一标识SP 可用于日志和去重 -->
Version="2.0" <!-- SAML 版本 -->
IssueInstant="2026-05-10T08:30:05Z" <!-- 响应签发时间(UTC) -->
Destination="https://sp.company.com/saml/acs" <!-- 目标 SP 的 ACS URL,SP 应校验此字段 -->
InResponseTo="a1b2c3d4-e5f6-7890-abcd-ef1234567890"> <!-- 对应 AuthnRequest 的 ID,将请求与响应关联 -->

<!-- Issuer:响应签发者(IdP)的 EntityID -->
<saml:Issuer>https://idp.company.com/adfs/services/trust</saml:Issuer>

<!-- IdP 的数字签名:确保 Response 的整体完整性。有两种层次:
1. 在 Response 层级签名(包裹 Assertion)
2. 在 Assertion 层级签名(更精细)
实际中常见的是两层都签,或者至少 Assertion 层级签名 -->
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="#r1234567-890a-bcde-f012-34567890abcd">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>base64-hash-of-signed-info</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>base64-signature-value</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>base64-idp-certificate</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>

<!-- Status:认证结果状态,必选!即使认证失败也会返回失败状态 -->
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
<!-- 可选的子状态码,提供更细粒度的错误原因 -->
<!-- <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder">
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:AuthnFailed"/>
</samlp:StatusCode> -->
<!-- 可选的状态消息,供 SP 展示给用户或日志 -->
<!-- <samlp:StatusMessage>Authentication failed: invalid credentials</samlp:StatusMessage> -->
</samlp:Status>

<!-- === Assertion(真正的"令牌") === -->
<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="a9876543-210f-edcb-a987-6543210fedcb" <!-- Assertion 的唯一标识 -->
IssueInstant="2026-05-10T08:30:05Z" <!-- Assertion 签发时间 -->
Version="2.0"> <!-- SAML 版本 -->

<!-- Assertion 的签发者(也必须是 IdP) -->
<saml:Issuer>https://idp.company.com/adfs/services/trust</saml:Issuer>

<!-- Optional: Assertion 层级的签名(推荐!这样即使 Response 被篡改,Assertion 也能独立验证) -->
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<!-- ... 结构同上,引用的是 Assertion 的 ID ... -->
</Signature>

<!-- ★ Subject(主体):标识被认证的用户是谁 ★ -->
<saml:Subject>
<!-- NameID:用户的唯一标识 -->
<saml:NameID
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
NameQualifier="https://idp.company.com/adfs/services/trust" <!-- IdP 的限定名 -->
SPNameQualifier="https://sp.company.com/saml/metadata"> <!-- SP 的限定名 -->
8f7e6d5c-4b3a-2910-fedc-ba9876543210 <!-- 用户的持久化唯一标识 -->
</saml:NameID>

<!-- SubjectConfirmation:防止 Assertion 被其他 SP 滥用 -->
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
NotOnOrAfter="2026-05-10T08:40:05Z" <!-- 确认数据过期时间 -->
Recipient="https://sp.company.com/saml/acs" <!-- 指定接收者(必须是目标 SP 的 ACS URL) -->
InResponseTo="a1b2c3d4-e5f6-7890-abcd-ef1234567890" <!-- 关联到请求 ID,防重放 -->
NotBefore="2026-05-10T08:30:00Z" /> <!-- 确认数据生效时间 -->
</saml:SubjectConfirmation>
</saml:Subject>

<!-- ★ Conditions(条件):限定 Assertion 的使用范围 ★ -->
<saml:Conditions
NotBefore="2026-05-10T08:30:00Z" <!-- Assertion 生效时间 -->
NotOnOrAfter="2026-05-10T08:40:05Z"> <!-- Assertion 过期时间(通常 5~10 分钟) -->

<!-- AudienceRestriction:限定只能被特定 Audience(即目标 SP)使用 -->
<saml:AudienceRestriction>
<saml:Audience>https://sp.company.com/saml/metadata</saml:Audience> <!-- 必须是目标 SP 的 EntityID -->
</saml:AudienceRestriction>

<!-- OneTimeUse:标记该断言只能使用一次(较少见) -->
<!-- <saml:OneTimeUse/> -->

<!-- ProxyRestriction:限制还能被代理给多少个其他 SP(级联信任场景) -->
<!-- <saml:ProxyRestriction Count="0"/> -->

</saml:Conditions>

<!-- ★ AuthnStatement(认证声明):描述认证发生的时间、方式 ★ -->
<saml:AuthnStatement
AuthnInstant="2026-05-10T08:30:03Z" <!-- 用户实际完成认证的时间不是签发时间-->
SessionIndex="si-9876543210" <!-- IdP 侧会话的唯一索引,用于 SLO(单点登出)关联 -->

SessionNotOnOrAfter="2026-05-10T20:30:00Z"> <!-- IdP 侧会话的过期时间 -->

<!-- AuthnContext:说明用户使用什么方式认证的 -->
<saml:AuthnContext>
<!-- AuthnContextClassRef:认证上下文引用,表明认证强度 -->
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
<!-- 或者用 AuthnContextDecl/AuthnContextDeclRef 引用外部描述
<saml:AuthnContextDecl>
<my:AuthnDetails xmlns:my="http://company.com/authn">
<my:AuthnMethod>password</my:AuthnMethod>
<my:MFAUsed>true</my:MFAUsed>
</my:AuthnDetails>
</saml:AuthnContextDecl> -->
</saml:AuthnContext>
</saml:AuthnStatement>

<!-- ★ AttributeStatement(属性声明):用户详细属性信息 ★ -->
<saml:AttributeStatement>
<saml:Attribute
Name="email" <!-- 属性名称 -->
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" <!-- 属性名称格式 -->
FriendlyName="Email"> <!-- 可读名称(可选) -->
<saml:AttributeValue xsi:type="xs:string">zhangsan@company.com</saml:AttributeValue>
</saml:Attribute>

<saml:Attribute
Name="department"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
FriendlyName="Department">
<saml:AttributeValue xsi:type="xs:string">Engineering</saml:AttributeValue>
</saml:Attribute>

<saml:Attribute
Name="role"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
FriendlyName="Role">
<saml:AttributeValue xsi:type="xs:string">admin</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">developer</saml:AttributeValue> <!-- 允许多个值 -->
</saml:Attribute>

<saml:Attribute
Name="groups"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
FriendlyName="Groups">
<saml:AttributeValue xsi:type="xs:string">Engineering-Team-A</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">VPN-Access</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>

</saml:Assertion>
</samlp:Response>

6.3 Response 顶层属性详解

属性 必选? 含义 说明
ID ✅ 是 响应唯一标识 UUID,SP 用于日志、去重
Version ✅ 是 SAML 版本 固定 2.0
IssueInstant ✅ 是 响应签发时间(UTC) SP 用于判断时效性
Destination ✅ 是 目标 SP ACS URL SP 必须校验此字段是否与自己的 ACS URL 一致,防止被发给错误的接收者
InResponseTo ❌ 否 对应的 AuthnRequest ID SP-Initiated 时有此字段,IdP-Initiated 时无此字段。SP 应校验此值是否与之前发送的请求 ID 匹配

6.4 状态码(Status)详解

<Status> 元素包含认证结果,由三部分组成:

StatusCode(状态码)

顶层状态码 含义
urn:oasis:names:tc:SAML:2.0:status:Success 成功——认证通过,Assertion 有效
urn:oasis:names:tc:SAML:2.0:status:Requester ❌ 请求方错误(SP 发起的请求有问题)
urn:oasis:names:tc:SAML:2.0:status:Responder ❌ 响应方错误(IdP 内部错误)
urn:oasis:names:tc:SAML:2.0:status:VersionMismatch ❌ 版本不匹配

子状态码(二级错误原因)

当顶层状态码为 RequesterResponder 时,子状态码提供更具体的原因:

子状态码 含义
AuthnFailed 用户认证失败(密码错误等)
InvalidAttrNameOrValue 属性名称或值无效
InvalidNameIDPolicy NameIDPolicy 不满足
NoAuthnContext 无法满足请求的认证上下文要求
NoAvailableIDP 没有可用的 IdP(Scoping 代理场景)
NoPassive IsPassive 请求无法满足(用户需要交互式登录)
NoSupportedIDP 没有支持的 IdP
ProxyCountExceeded 代理级数超限
RequestDenied 请求被拒绝(一般是配置问题)
RequestUnsupported 不支持的请求类型
RequestVersionDeprecated 请求版本已废弃
RequestVersionTooHigh 请求版本过高
RequestVersionTooLow 请求版本过低
ResourceNotRecognized 资源无法识别
TooManyResponses 响应过多
UnknownAttrProfile 未知属性配置文件
UnknownPrincipal 未知的 Principal
UnsupportedBinding 不支持的 Binding

StatusMessage

可选的描述性文本,IdP 用于传入更多错误详情(如”User account is locked”)。

6.5 Assertion 核心字段详解

Issuer(断言签发者)

Assertion 中的 <Issuer> 必须是 IdP 的 EntityID。SP 应校验此值是否匹配信任的 IdP。

Subject(主体)

Subject 下的三大组件

① NameID

用户唯一标识。SP 通常用这个值作为用户在本地的唯一标识(如关联到数据库中的用户记录)。

② SubjectConfirmation(主体确认)

这是 SAML 重要的安全机制——防止 Assertion 被恶意 SP 或第三方劫持。它的工作方式如下:

Method 属性定义了确认方式:

Method 含义
...:cm:bearer Bearer(最常用):持有 Assertion 的 SP 即可使用——谁拿到 Assertion,谁就是被授权方。依赖 HTTP-POST 的接收者校验
...:cm:holder-of-key Holder-of-Key:SP 必须出示自己的私钥签名才能确认持有 Assertion(更严格,但实现复杂)
...:cm:sender-vouches Sender-Vouches:由消息发送方担保,无需 SP 做额外操作,但需要信任传输信道

SubjectConfirmationData 中的属性:

属性 含义 说明
NotOnOrAfter ✅ 确认数据过期时间 SP 必须校验当前时间在此时间之前
NotBefore ❌ 确认数据生效时间 在此之前 Assertion 无效
Recipient ✅ 目标接收者的 URL SP 必须校验此 URL 是否等于自己的 ACS URL——这是防止中间人劫持的核心手段
InResponseTo ❌ 对应的请求 ID SP 必须校验此值与之前发出的 AuthnRequest ID 一致——这是防重放的核心手段
Address ❌ 发起请求的 IP 地址 IdP 可以记录用户的来源 IP,SP 可做额外校验

⭐ 安全要点:SP 收到 Assertion 后,务必校验 RecipientInResponseTo(如有)和 NotOnOrAfter。很多 SAML 安全漏洞就是因为 SP 忽略了这些校验。

Conditions(条件)

属性 含义
NotBefore Assertion 生效时间(UTC)
NotOnOrAfter Assertion 过期时间(UTC),通常比 AuthnInstant 晚 5~10 分钟(短有效期 = 更高的安全性)

Condition 子元素

① AudienceRestriction(受众限制)⭐

1
2
3
<saml:AudienceRestriction>
<saml:Audience>https://sp.company.com/saml/metadata</saml:Audience>
</saml:AudienceRestriction>
  • 作用:限定这个 Assertion 只能被指定的 SP 使用
  • SP 必须校验:检查 <Audience> 中是否包含自己的 EntityID。如果不包含,必须拒绝该 Assertion
  • 这是防止”Assertion 注入”攻击的关键——防止 X SP 的 Assertion 被拿来登录 Y SP。

② OneTimeUse

标记该 Assertion 只能使用一次。SP 收到后应记录下来,再次收到相同 ID 的 Assertion 应拒绝。

③ ProxyRestriction

限制该 Assertion 还能被代理给多少个其他 SP(级联信任链场景)。Count 表示允许的最高级数。

AuthnStatement(认证声明)

属性 含义
AuthnInstant ✅ 用户完成认证的时间(UTC)。重要!SP 可用此字段计算用户认证时长
SessionIndex ❌ IdP 侧会话索引。在 SLO(单点登出)中非常关键——SP 和 IdP 通过此值关联同一次登录会话
SessionNotOnOrAfter ❌ IdP 侧会话的过期时间

AuthnContext 中的认证强度 URI(与 AuthnRequest 中的一致,见 5.4 节)。

AttributeStatement(属性声明)

属性 含义
Name ✅ 属性名——IdP 和 SP 之间约定好的字段名(如 emaildepartment
NameFormat ❌ 属性名的命名格式(见下方)
FriendlyName ❌ 可读属性名(对人友好,如 “Email”、”Department”)
<AttributeValue> 属性值,允许多个值(如多个角色)

NameFormat 可选值

URI 含义
urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified 未指定,由 SP 和 IdP 自行约定
urn:oasis:names:tc:SAML:2.0:attrname-format:uri 属性名是 URI 格式,如 urn:oid:0.9.2342.19200300.100.1.3(邮箱的 OID)
urn:oasis:names:tc:SAML:2.0:attrname-format:basic 最常用,属性名是简单字符串

常见标准属性(OID 映射)

常用属性 OID SAML 属性名
邮箱 urn:oid:0.9.2342.19200300.100.1.3 mailemail
姓名 urn:oid:2.5.4.42 givenName
姓氏 urn:oid:2.5.4.4 sn
工号 urn:oid:2.16.840.1.113730.3.1.3 employeeID
部门 urn:oid:2.5.4.12 department
电话 urn:oid:2.5.4.20 telephoneNumber
角色/组 自定义 groupsmemberOf

实际企业场景中,SP 和 IdP 通过元数据(Metadata)协商确定 IdP 需要返回哪些属性,SP 需要消费哪些属性。

6.6 通过 HTTP-POST Binding 传输 Response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- IdP 返回给浏览器的 HTML 页面(自动提交表单) -->
<html>
<body>
<form method="POST" action="https://sp.company.com/saml/acs">
<!-- SAMLResponse:包含整个 Response XML 的 Base64 编码 -->
<input type="hidden" name="SAMLResponse" value="base64-encoded-xml-response" />
<!-- RelayState:SP 在请求中传过来的或 IdP 指定的回跳地址 -->
<input type="hidden" name="RelayState" value="https://sp.company.com/dashboard" />
</form>
<script>
document.forms[0].submit(); <!-- 页面加载后自动提交表单 -->
</script>
</body>
</html>
POST Body 参数 描述
SAMLResponse 完整的 SAML Response XML,经 Base64 编码
RelayState 回跳地址。SP 应校验此值是否合法(防止开放重定向攻击)

注意:HTTP-POST Binding 的 SA MLResponse 与 HTTP-Redirect Binding 的 SAMLRequest 不同——Response 不经过 deflate 压缩,只做 Base64 编码。因为 POST body 没有长度限制,不需要压缩。

6.7 SP 收到 Response 后的校验清单(必做)

SP 接收到 SAML Response 后,必须执行以下校验(任何一项失败,SP 必须拒绝该 Assertion):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
graph TD
A[收到 SAML Response] --> B{Base64 解码成功?}
B -->|否| Z[拒绝]
B -->|是| C{XML 格式正确?}
C -->|否| Z
C -->|是| D{签名验证通过?}
D -->|否| Z
D -->|是| E{Destination = 我的 ACS URL?}
E -->|否| Z
E -->|是| F{Status = Success?}
F -->|否| Z[拒绝并处理错误]
F -->|是| G{当前时间在 Conditions 窗口内?}
G -->|否| Z
G -->|是| H{Audience 包含我的 EntityID?}
H -->|否| Z
H -->|是| I{Recipient = 我的 ACS URL?}
I -->|否| Z
I -->|是| J{InResponseTo 匹配我发出的请求?}
J -->|否| Z
J -->|是| K{此 Assertion ID 是否已用过?}
K -->|是| Z[拒绝并告警-重放攻击!]
K -->|否| L[✅ 认证通过,建立本地会话]

校验项详细说明

序号 校验项 为什么重要
1 XML 签名验签 使用 IdP 公钥验证 XML 签名(Response 级别和/或 Assertion 级别),确保报文未被篡改
2 Destination 验证 Response 的 Destination 等于 SP 的 ACS URL,防止响应被非法重定向
3 Status 必须是 Success,否则 SSO 失败
4 Conditions.NotBefore / NotOnOrAfter 验证当前时间在 Assertion 有效期内(严格校验,时钟偏差可配置一个容忍窗口如 ±3 分钟)
5 AudienceRestriction 验证 <Audience> 中包含 SP 的 EntityID,防止 Assertion 被其他 SP 使用
6 SubjectConfirmationData.Recipient 验证等于 SP 的 ACS URL,防止 Assertion 被截获后发给另一个 URL
7 SubjectConfirmationData.InResponseTo 验证等于 SP 此前发出的 AuthnRequest ID(如果有),防重放
8 SubjectConfirmationData.NotOnOrAfter 验证确认数据未过期
9 重放检测 记录已使用的 Assertion ID,相同 ID 再次出现说明是重放攻击
10 Issuer 验证 Issuer 是受信任的 IdP 的 EntityID

七、SAML 绑定方式详解

前面介绍了最常用的 HTTP-Redirect 和 HTTP-POST,这里再系统对比所有 Binding。

7.1 四种绑定对比

Binding 方向 传输方式 消息编码 优点 缺点
HTTP-Redirect SP → IdP HTTP GET(302 重定向) URL Query 参数,Deflate + Base64 轻量、适合浏览器重定向 URL 长度受限(通常 8KB),只能单向
HTTP-POST 双向 HTTP POST(HTML 表单) POST Body,Base64 无长度限制,可传完整签名 XML 涉及浏览器渲染 HTML 表单
HTTP-Artifact IdP → SP 先传短 code,再服务端取 Artifact 码 + SOAP 回连 适合超长报文,减少浏览器暴露 多一次服务端 HTTP 请求,延迟增加
SOAP SP ↔ IdP 服务端直连 HTTP POST SOAP Envelope 服务端可靠通信 不支持浏览器参与

7.2 HTTP-Artifact Binding 详解

当 SAML Response 的 XML 过大(超过浏览器 URL/POST 限制)或者有更严格的安全要求时,可使用 Artifact Binding:

流程

1
2
3
4
1. IdP 返回一个短的 Artifact 字符串(约 40 字符)给浏览器
2. 浏览器 POST 这个 Artifact 到 SP 的 ACS URL
3. SP 收到 Artifact 后,通过 SOAP 服务端直连 IdP 的 Artifact Resolution 端点
4. IdP 返回实际的 SAML Response XML

Artifact 格式

1
2
Artifact = TypeCode (2字节) + EndpointIndex (2字节) + RemainingArtifact (20字节随机数)
例如:AAQAADWNe9Pv0C6GXh8TqP0HkB8fGjFkIhJm

优缺点

  • ✅ 浏览器只传递短 code,敏感数据不经过浏览器(更安全)。
  • ✅ 无长度限制。
  • ❌ 需要 SP 能反向连接 IdP(防火墙可能限制出站)。
  • ❌ 多一次 SOAP 调用,延迟增加。

八、元数据(Metadata)

SP 和 IdP 之间的信任关系通过交换元数据建立。元数据是一个 XML 文件,描述了实体的配置、端点、证书、能力等。

8.1 SP 元数据示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0"?>
<md:EntityDescriptor
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
entityID="https://sp.company.com/saml/metadata">

<!-- SP 描述 -->
<md:SPSSODescriptor
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"
AuthnRequestsSigned="true" <!-- SP 发出的 AuthnRequest 是否签名 -->
WantAssertionsSigned="true"> <!-- SP 要求 IdP 签名 Assertion -->

<!-- SP 的数字签名证书(IdP 用此验证 SP 签名的 AuthnRequest) -->
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>base64-certificate</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>

<!-- SP 的加密证书(IdP 用此加密 Assertion 中的敏感信息) -->
<md:KeyDescriptor use="encryption">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>base64-encryption-certificate</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>

<!-- ACS(Assertion Consumer Service)端点:IdP 把 Response POST 到这里 -->
<md:AssertionConsumerService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://sp.company.com/saml/acs"
index="0"
isDefault="true"/>

<!-- 可选:如果要支持 Artifact,还需要声明 ArtifactResolutionService -->

<!-- 可选:SP 希望 IdP 返回哪些属性的声明 -->
<md:AttributeConsumingService index="0">
<md:ServiceName xml:lang="en">My Company App</md:ServiceName>
<md:RequestedAttribute Name="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
isRequired="true"/>
<md:RequestedAttribute Name="department" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
isRequired="false"/>
<md:RequestedAttribute Name="role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
isRequired="true"/>
</md:AttributeConsumingService>

</md:SPSSODescriptor>
</md:EntityDescriptor>

8.2 IdP 元数据示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0"?>
<md:EntityDescriptor
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
entityID="https://idp.company.com/adfs/services/trust">

<md:IDPSSODescriptor
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"
WantAuthnRequestsSigned="true"> <!-- IdP 要求 SP 签名 AuthnRequest -->

<!-- IdP 的签名证书(SP 用此验证 IdP 签名的 Response) -->
<md:KeyDescriptor use="signing">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>base64-idp-certificate</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>

<!-- IdP 的加密证书(SP 用此公钥加密 Assertion 中的敏感信息,让 IdP 才能解密) -->
<md:KeyDescriptor use="encryption">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>base64-encryption-cert</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>

<!-- IdP 的单点登录端点(接收 AuthnRequest) -->
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://idp.company.com/sso/redirect"/>
<md:SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp.company.com/sso/post"/>

<!-- IdP 的单点登出端点 -->
<md:SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://idp.company.com/slo/redirect"/>
<md:SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp.company.com/slo/post"/>

</md:IDPSSODescriptor>
</md:EntityDescriptor>

8.3 元数据中的关键要素

字段 含义
entityID 全局唯一的实体标识,是 SP/IdP 的”身份证号”
KeyDescriptor[use=signing] 签名用的证书(对方用于验签)
KeyDescriptor[use=encryption] 加密用的证书(对方用于加密,我方解密)
AssertionConsumerService SP 接收 SAML Response 的端点(核心!SP 的”收件地址”)
SingleSignOnService IdP 接收 AuthnRequest 的端点(核心!IdP 的”收件地址”)
SingleLogoutService 单点登出端点

8.4 元数据交换方式

方式 描述
手工导入 一方导出元数据 XML 文件,另一方导入到系统中。最常见的方式,适合 SP 和 IdP 数量都不多的情况
URL 发布 通过 HTTP 发布元数据端点(如 https://idp.company.com/FederationMetadata/2007-06/FederationMetadata.xml),对方定时轮询获取
标准化注册 通过中间注册中心,如公司的元数据交换平台,自动分发

九、安全机制

SAML 的安全性保障主要来自以下几个方面。

9.1 XML 数字签名(XML Digital Signature / XMLDSig)

为什么需要签名?

浏览器传输 SAML 消息时(HTTP-Redirect 或 HTTP-POST),消息经过中间节点可能被篡改。签名可以保证:

  1. 完整性:报文未被篡改。
  2. 来源认证:报文确实来自合法的 IdP/SP。

签名结构概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<Signature>
<SignedInfo> ← 指定哪些信息被签名
<CanonicalizationMethod/> ← 规范化算法(XML 有等效的多种写法,需要规范化)
<SignatureMethod/> ← 签名算法
<Reference URI="#assertion-id"> ← 引用被签名的元素(通过 ID)
<Transforms/> ← 签名前应用的转换
<DigestMethod/> ← 哈希算法
<DigestValue/> ← 哈希值
</Reference>
</SignedInfo>
<SignatureValue/> ← 实际的签名值
<KeyInfo> ← 签名者证书信息(接收方用此获取公钥验签)
<X509Data>
<X509Certificate/> ← 签名者的 X.509 证书
</X509Data>
</KeyInfo>
</Signature>

关键点

  • XML 签名支持多种签名粒度:可以签整个 Response,也可以只签 Assertion,甚至可以只签特定元素。
  • Canonicalization(规范化):XML 文档有多种等效写法(如标签换行、属性顺序等),规范化算法将其统一为标准形式,确保相同内容的 XML 产生相同的哈希值。
  • 推荐使用 Exclusive XML Canonicalization(xml-exc-c14n,避免命名空间声明干扰签名结果。

9.2 XML 加密(XML Encryption)

SAML 规范支持对 Assertion 中的敏感属性(如个人身份信息)进行加密,确保只有目标 SP 能解密。

  • IdP 使用 SP 的公钥(来自 SP 元数据的 KeyDescriptor[use=encryption])对敏感 XML 元素加密。
  • SP 使用自己的私钥解密。

9.3 短有效期

策略 典型值 说明
AuthnRequest 有效期 ±1~3 分钟 防止请求被截获重放
Assertion 有效期 5~10 分钟 大多数 SSO 场景足够,短窗口降低重放风险
Session 有效期 通常 8~12 小时 对应一个工作日,IdP 侧会话

9.4 Audience 限制与 Recipient 校验(双重防线)

  • Audience 限制:Assertion 中的 <Audience> 明确指定了哪个 SP 可以使用,其他 SP 收到后必须拒绝。
  • Recipient 校验<SubjectConfirmationData>Recipient 指定了 ACS URL,即使 Assertion 落到其他系统手上,也会因为 URL 不匹配而校验失败。

9.5 签名算法的安全实践

算法 推荐? 说明
RSA-SHA256 推荐 当前主流标准
RSA-SHA1 不推荐 SHA-1 有碰撞风险
DSA-SHA1 不推荐 已逐步废弃
RSA-SHA512 ✅ 可以用 更安全但部分旧 IdP 可能不支持

十、单点登出(SLO - Single Logout)

SAML 支持”一次登出,全部登出”的 SLO 流程。

10.1 SLO 基本流程

1
2
3
4
5
6
7
8
9
10
11
12
sequenceDiagram
participant User as 用户
participant SP1 as SP(Jira)
participant SP2 as SP(Confluence)
participant IdP as Identity Provider

User->>SP1: 点击登出
SP1->>IdP: 发送 LogoutRequest(携带 SessionIndex)
IdP->>SP2: 向其他活跃 SP 发送 LogoutRequest
SP2-->>IdP: 返回 LogoutResponse(成功)
IdP-->>SP1: 返回 LogoutResponse(携带所有 SP 的结果)
SP1->>User: 登出完成,清除 IdP 会话

10.2 LogoutRequest 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<samlp:LogoutRequest
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="logout-request-id-xyz"
Version="2.0"
IssueInstant="2026-05-10T09:00:00Z"
Destination="https://idp.company.com/slo/post"
NotOnOrAfter="2026-05-10T09:05:00Z">

<saml:Issuer>https://sp.company.com/saml/metadata</saml:Issuer>

<!-- 签名 -->
<Signature>...</Signature>

<!-- 要登出的用户 NameID(必须与登录时收到的 Assertion 中的 NameID 一致) -->
<saml:NameID
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">
8f7e6d5c-4b3a-2910-fedc-ba9876543210
</saml:NameID>

<!-- 会话索引,关联到 AuthnStatement 中的 SessionIndex -->
<samlp:SessionIndex>si-9876543210</samlp:SessionIndex>
</samlp:LogoutRequest>

10.3 SLO 的两种传输方式

方式 描述 优缺点
Front-Channel(前端通道) 通过浏览器重定向或 POST 传递(用户可见) 依赖浏览器,用户体验受影响
Back-Channel(后端通道) SP 和 IdP 之间通过 SOAP 服务端直连 更快更可靠,用户无感知。但要求 SP 服务端能连接 IdP

实际上,SLO 的实现比 SSO 复杂得多,很多 IdP 和 SP 的 SLO 实现并不完美,可能出现”部分登出”(部分 SP 成功、部分失败)的情况。企业场景中,更常见的策略是让 IdP 侧会话过期自动登出所有 SP。

十一、SAML 的优缺点总结

11.1 优点

优点 说明
成熟稳定 SAML 2.0 自 2005 年发布,已在全球大量企业部署,经得起考验
企业生态丰富 几乎所有企业级 Saas(Salesforce、Workday、Office 365、Jira、Confluence)都支持 SAML SSO
单点登录体验好 一次登录,多系统可用(在 Web 场景下体验非常流畅)
安全机制完善 XML 签名、加密、短有效期、Audience 限制、Recipient 校验等多层防护
属性传递标准 可附带丰富的用户属性(角色、部门、组等),SP 无需额外 API 调用
联邦场景成熟 支持跨组织的身份联盟(B2B),不同公司的 IdP 可互相操作

11.2 缺点

缺点 说明
XML 冗长复杂 SAML 报文比 JWT 大得多,解析和签名验证的开销也更大
移动端支持差 依赖浏览器和 HTTP-POST/Redirect 绑定,原生 App 中实现 SAML 困难(需要用 WebView,体验不佳)
实现难度高 完整实现 SAML 规范(处理各种签名、绑定、编码方式)远比 OIDC 复杂
灵活性不足 SAML 主要围绕”浏览器-重定向-Backend”模型,对 SPA、前后端分离场景支持差
无 API 授权能力 SAML 专注于认证,不做 API 授权(不像 OAuth2/Access Token 那样可用于鉴权微服务调用)

11.3 何时选 SAML,何时选 OIDC?

场景 推荐
公司接入 Salesforce、Jira、Slack 等企业 SaaS SAML(这些系统通常只支持 SAML)
新建设的企业内部 SSO 系统 OIDC(现代、轻量、生态好)
移动端 App 登录 OIDC(不要用 SAML)
政府/金融/合规严格的场景 SAML(成熟、审计链路清晰)
零信任架构、微服务鉴权 OIDC / OAuth2
B2B 跨公司联邦登录 SAML(生态成熟)或 OIDC(新建可选)

十二、小结

  • SAML(Security Assertion Markup Language)是一种基于 XML 的企业级认证+属性传递协议,主要解决”一次登录,多处访问”的 SSO 问题。
  • 三大核心角色:用户(Principal)、服务提供方(SP,如业务系统)、身份提供方(IdP,如统一认证中心)。
  • 核心流程SP-Initiated SSO(SP 发起,最常用)和 IdP-Initiated SSO(IdP 门户发起)。
  • AuthnRequest 是 SP 发给 IdP 的认证请求,包含 SP 身份、期望的 NameID 格式、认证强度要求等。
  • Response + Assertion 是 IdP 返回给 SP 的”身份令牌”,包含用户唯一标识(NameID)、认证时间、认证方式、用户属性,以及 Audience 限制、Recipient、签名等安全机制。
  • SP 收到 Response 后必须执行严格的校验(签名、时间、Audience、Recipient、重放检测等),任何一项校验失败都应拒绝。
  • 元数据(Metadata)是 SP 和 IdP 之间建立信任的基石,通过交换 XML 元数据来同步端点、证书和配置。
  • 安全机制主要依赖 XML 数字签名、短有效期、Audience 限制和 Recipient 校验。
  • SLO(单点登出)实现比 SSO 复杂,实际企业场景需仔细测试。
  • 新系统优先选择 OIDC,但接入企业现有 SaaS / 合规要求高的场景仍大量使用 SAML

SAML 虽然诞生已有二十年,XML 也很”重”,但在企业级 SSO 和 B2B 联邦领域,它依然是不可替代的事实标准。理解 SAML 的核心概念和报文细节,对于企业架构师、SaaS 集成开发和身份认证领域的工程师来说,仍然是一项重要的技能。