Skip to content

参考

基于 OASIS SAML 2.0 Technical Overview(sstc-saml-tech-overview-2.0)/ SAML 2.0 Bindings(saml-bindings-2.0-os)/ SAML 2.0 Approved Errata 05 / OWASP SAML Security Cheat Sheet / NIST SP 800-131A Rev.2 / RFC 7522 官方文档编写

速查

  • 协议定位:SAML 2.0 = 基于 XML 的身份联邦 + SSO 协议(OASIS 2005),不是授权协议(无 Access Token)
  • 三角色:IdP(认证 + 签发断言)/ SP(消费断言 + 授访问)/ Principal(被认证主体)
  • Assertion 三类 Statement:Authentication(认证方式)/ Attribute(属性)/ Authorization Decision(少用)
  • NameID 五种 Format:transient / persistent / email-address / X509SubjectName / entity
  • SubjectConfirmation 三种 Method:bearer(最常用)/ holder-of-key / sender-vouches
  • Bindings:HTTP Redirect(DEFLATE+URL query)/ HTTP POST(Base64+表单字段,断言必须签名)/ HTTP Artifact(固定长度 artifact + 后端 SOAP)/ SAML SOAP / PAOS/ECP
  • XML 签名:enveloped + C14N 规范化 + 两阶段验证(Digest + SignatureValue)
  • 签名算法:最低 RSA-SHA-256,禁 SHA-1(NIST 2030-12-31 前全面退役)
  • Replay 防御组合拳:InResponseTo 关联 + Assertion ID 去重缓存 + 极短 NotOnOrAfter(典型 1 分钟)+ OneTimeUse
  • 跨 SP 重放防御三字段:Destination=ACS URL、Audience=SP EntityID、Recipient=ACS URL
  • XSW 防御:wantAssertionsSigned=true + wantAuthnResponseSigned=true + 绝对 XPath + Reference URI 与业务节点一致
  • 签名证书:独立于 TLS、最长 2 年、HSM 保护、通过 metadata URL 发布、忽略文档内 <ds:KeyInfo>
  • TLS 1.2+:所有 SAML 通信走加密通道
  • Node 主流库tngan/samlify(默认 RSA-SHA256)/ @node-saml/passport-samlsignatureAlgorithm: 'sha256')/ node-saml/xml-crypto(XML 签名底层库)
  • 完整说明见 入门 / 深度

三角色完整职责表

角色全称职责关键输出典型实现
IdPIdentity Provider认证 Principal、签发已签名 Assertion、维护用户目录与属性<saml:Assertion> + <ds:Signature>Azure AD / Okta / ADFS / OneLogin / Ping / Keycloak / Shibboleth IdP
SPService Provider接收并验证 Assertion、提取 Subject/Attributes、授予业务访问业务会话(Cookie / JWT)Salesforce / ServiceNow / Workday / 自建 Node SP(samlify / passport-saml)
Principal被认证主体通常为终端用户,被 IdP 认证后跨 SP 免密访问/浏览器中的用户

Assertion 顶层结构

元素 / 属性含义SP 必校验
<saml:Assertion ID>唯一标识是(去重缓存防 Replay)
Version协议版本(2.0)是(必须为 2.0)
IssueInstant签发时间(UTC ISO 8601)是(推断时效)
<saml:Issuer>签发方 EntityID必须等于可信 IdP EntityID
<ds:Signature>XML 签名(enveloped)必须独立验证(wantAssertionsSigned)
<saml:Subject>主体(NameID + SubjectConfirmation)
<saml:Conditions>有效条件(时间窗口、Audience)必须全部校验
<saml:AuthnStatement>认证声明是(AuthnContextClassRef 匹配策略)
<saml:AttributeStatement>属性声明应用层消费
<saml:AuthzDecisionStatement>授权决策(少用)应用层判断

NameID Format 完整表

Format URI短名隐私强度适用场景
urn:oasis:names:tc:SAML:2.0:nameid-format:transienttransient最高(每次会话新假名)IdP-Initiated SSO、最强隐私
urn:oasis:names:tc:SAML:2.0:nameid-format:persistentpersistent高(按 SP 隔离持久假名)需跨会话关联用户、又怕跨 SP 追踪
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressemail-address低(邮箱可复用、变更)简单内部场景;邮箱变更即关联断裂
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectNameX509SubjectName中(依赖 PKI)证书强身份场景
urn:oasis:names:tc:SAML:2.0:nameid-format:entityentityN/A非人类主体(服务间身份)
urn:oasis:names:tc:SAML:2.0:nameid-format:unspecifiedunspecified最弱兼容老系统

SubjectConfirmation Method 完整表

Method URI短名安全强度适用场景
urn:oasis:names:tc:SAML:2.0:cm:bearerbearer中(持有即用)浏览器 SSO(事实标准,95% 部署)
urn:oasis:names:tc:SAML:2.0:cm:holder-of-keyholder-of-key最高(须证明掌握密钥)高敏感场景、ECP/PAOS
urn:oasis:names:tc:SAML:2.0:cm:sender-vouchessender-vouches低(依赖发送方担保)中间代理场景,少用

AuthnContextClassRef 常见取值

ClassRef URI含义
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport用户名密码 + TLS
urn:oasis:names:tc:SAML:2.0:ac:classes:Password仅用户名密码(无 TLS)
urn:oasis:names:tc:SAML:2.0:ac:classes:TimesyncToken时间同步 MFA(TOTP 类)
urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard智能卡 PKI
urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI智能卡 PKI(细化)
urn:oasis:names:tc:SAML:2.0:ac:classes:KerberosKerberos 票据
urn:oasis:names:tc:SAML:2.0:ac:classes:UnspecifiedIdP 不透露(最弱)

Bindings 完整对比表

Binding编码方式传递方式签名要求长度限制典型用途
HTTP RedirectDEFLATE + Base64 + URL queryHTTP 302 重定向URL 可单独签名(少用)(URL ~2000-8000 字节)AuthnRequest、LogoutRequest(短消息)
HTTP POSTBase64 + HTML 表单隐藏字段浏览器自动 POST 表单断言必须签名含已签名 Assertion 的 Response
HTTP Artifact固定长度 artifactGET 或 POST 传 artifactArtifact 解析通道(SOAP)独立签名否(artifact 固定长度)极敏感场景、断言过大
SAML SOAPSOAP 信封HTTP SOAP(后端直连)整条 SOAP 可签名ArtifactResolve、AttributeQuery、NameIDMapping
PAOS / ECP反向 SOAPHTTP Accept: application/vnd.paos+xml同 SOAP非浏览器客户端(ECP)

HTTP Redirect 编码流程

text
1. SAML 消息 XML → UTF-8 字节
2. DEFLATE 压缩(zlib,无压缩头)
3. Base64 编码
4. URL 编码(percent-encoding)
5. 拼为 URL query:?SAMLRequest=...&RelayState=...
6. 浏览器 302 重定向

HTTP POST 编码流程

text
1. SAML 消息 XML → UTF-8 字节
2. Base64 编码(不 DEFLATE)
3. 放入 HTML 表单隐藏字段 SAMLResponse
4. 浏览器自动 POST 到目标 URL

SP 必校验字段汇总

字段位置期望值拒绝规则
<saml:Issuer>(Response)Response可信 IdP EntityID不在白名单立即拒绝
<saml:Issuer>(Assertion)Assertion同 IdP EntityID不匹配立即拒绝
<ds:Signature>(Response)Response验签通过wantAuthnResponseSigned=true
<ds:Signature>(Assertion)Assertion验签通过wantAssertionsSigned=true
InResponseTo(Response)Response之前发出的 AuthnRequest ID不匹配立即拒绝(IdP-Initiated 例外)
InResponseTo(SubjectConfirmationData)Assertion 内同上同上
Destination(Response)ResponseSP 的 ACS URL不匹配立即拒绝(防跨 SP 重放)
Audience(AudienceRestriction)ConditionsSP 的 EntityID不包含立即拒绝(防跨 SP 重放)
Recipient(SubjectConfirmationData)Assertion 内SP 的 ACS URL不匹配立即拒绝(防跨 SP 重放)
NotBefore / NotOnOrAfterConditions当前时间在窗口内超出立即拒绝(典型窗口 1 分钟)
ID(Assertion)Assertion不在去重缓存重复立即拒绝(防 Replay)
<samlp:Status>ResponseSuccess非 Success 按错误处理

XML 签名算法与状态

算法 URI短名NIST 状态samlify/passport-saml 默认
http://www.w3.org/2001/04/xmldsig-more#rsa-sha256RSA-SHA-256接受(推荐)samlify 默认
http://www.w3.org/2001/04/xmldsig-more#rsa-sha384RSA-SHA-384接受可选
http://www.w3.org/2001/04/xmldsig-more#rsa-sha512RSA-SHA-512接受可选
http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256ECDSA-SHA-256接受可选
http://www.w3.org/2000/09/xmldsig#rsa-sha1RSA-SHA-1禁用(2030-12-31 前全面退役)不推荐
http://www.w3.org/2000/09/xmldsig#dsa-sha1DSA-SHA-1禁用不推荐
http://www.w3.org/2000/09/xmldsig#hmac-sha1HMAC-SHA-1禁用于签名不推荐
DigestMethod URI短名状态
http://www.w3.org/2001/04/xmlenc#sha256SHA-256推荐
http://www.w3.org/2001/04/xmldsig-more#sha384SHA-384接受
http://www.w3.org/2001/04/xmlenc#sha512SHA-512接受
http://www.w3.org/2000/09/xmldsig#sha1SHA-1禁用

SAML vs OIDC 完整对比表

维度SAML 2.0OIDC(OpenID Connect)
发布年份2005(OASIS)2014(OpenID Foundation)
数据格式XML AssertionJSON + JWT(JWS 签名)
协议范围纯身份认证 + 属性断言(无 Access Token)OAuth 2.0 之上的身份层,含 Access Token + ID Token + 授权
协议角色IdP / SP / PrincipalOP(OpenID Provider)/ RP(Relying Party)/ End-User
元数据交换XML metadata 互换(预共享 XML).well-known/openid-configuration 即时发现 + JWKS
传输流程表单 POST + 浏览器重定向Authorization Code Flow(GET 重定向 + 后端 token 端点交换)
签名机制XMLDSig(enveloped + C14N)JWS(Compact Serialization,Base64URL)
签名算法RSA-SHA-256(默认)/ ECDSA-SHA-256RS256 / ES256 / PS256
吊销难(短时效,无标准吊销协议;SLO 不保证传播)Refresh Token 轮换 + 复用检测(RFC 9700)
属性传递<AttributeStatement> 内多个 <saml:Attribute>ID Token claims + UserInfo 端点 + 标准 claims(sub/email/profile)
典型场景传统企业浏览器(Salesforce / ServiceNow / 政府联邦)SPA + 移动端 + API-first
元数据复杂度高(XML、命名空间、签名证书嵌入)低(JSON、JWKS、自动发现)
API 授权无(需另接 OAuth 2.0 或 RFC 7522)内建(Access Token + scopes)
PKCE 支持无(依赖 TLS)Authorization Code Flow + PKCE(RFC 7636)
库实现(Node)samlify / passport-saml / xml-cryptojose / jsonwebtoken / openid-client / panva/oauth4webapi
库实现(Java)java-saml / OpenSAMLnimbus-jose-jwt / pac4j
库实现(Python)python3-saml / PySAML2authlib / python-jose
元数据发布通过 metadata URL(受 TLS 保护).well-known/openid-configuration
会话管理IdP session + SP session(独立)IdP session + RP session(独立)+ 前端/后端通道退出
Single LogoutSAML SLO Profile(不保证传播)OIDC RP-Initiated Logout / Back-Channel Logout
响应大小大(几 KB,XML + 签名)小(几百字节,JWT)
学习曲线陡(XML、命名空间、签名、绑定、profile)平缓(JSON、REST)

常见 IdP 厂商对照

厂商EntityID 示例备注
Azure AD(Entra ID)https://login.microsoftonline.com/<tenant-id>/同时支持 SAML 与 OIDC,跨应用协调
Oktahttp://www.okta.com/exk<id>同时支持 SAML 与 OIDC
ADFShttps://adfs.example.com/federationmetadata/2007-06/federationmetadata.xmlMicrosoft 自建 IdP
OneLoginhttps://app.onelogin.com/saml/metadata/<app-id>同时支持 SAML 与 OIDC
Ping Identityhttps://ping.example.com/idp/samlmetadata企业级
Keycloakhttps://keycloak.example.com/realms/<realm>开源,同时支持 SAML 与 OIDC
Shibboleth IdPhttps://idp.example.com/idp/shibboleth学术 / 政府联邦主流
Google Workspacehttps://accounts.google.com/o/saml2?idpid=<id>仅 SAML(OIDC 是另一产品)

典型代码片段

samlify SP 配置

ts
import * as samlify from 'samlify';

// SP 配置(关键安全开关)
export const sp = samlify.ServiceProvider({
  entityID: 'https://sp.example.com/metadata',
  authnRequestsSigned: true,
  wantAssertionsSigned: true,           // ★ 强制断言独立签名
  wantMessageSigned: true,              // ★ 强制 Response 签名
  requestSignatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
  wantLogoutResponseSigned: true,
  wantLogoutRequestSigned: true,
  assertionConsumerService: [{
    Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
    Location: 'https://sp.example.com/acs',
  }],
});

// IdP 配置(预共享 metadata)
export const idp = samlify.IdentityProvider({
  metadata: fs.readFileSync('./idp-metadata.xml'),
});

samlify 处理登录请求与响应

ts
import { sp, idp } from './saml-config';

// SP → IdP:生成 AuthnRequest URL
app.get('/saml/login', async (req, res) => {
  const { id, context } = sp.createLoginRequest(idp, 'redirect');
  req.session.authnRequestId = id;  // ★ 保存请求 ID 用于 InResponseTo 校验
  res.redirect(context);
});

// IdP → SP:ACS 端点接收 Response
app.post('/acs', express.urlencoded({ extended: false }), async (req, res) => {
  try {
    const { extract, samlContentXml } = await sp.parseLoginResponse(idp, 'post', req);
    // ★ samlify 已自动验证 Response 签名 + Assertion 签名 + InResponseTo + Destination/Audience
    // 但 Assertion ID 去重需应用层维护
    const assertionId = extract.request.id; // 实际字段以库版本为准
    if (await redis.get(`saml:assertion:${assertionId}`)) {
      return res.status(401).send('Replay detected');
    }
    await redis.set(`saml:assertion:${assertionId}`, '1', 'EX', 600);

    // 提取 NameID + Attributes
    req.session.user = {
      nameID: extract.nameID,
      email: extract.attributes['email'],
      role: extract.attributes['role'],
    };
    res.redirect(extract.relaysate || '/dashboard');
  } catch (e) {
    res.status(401).send('SAML validation failed');
  }
});

passport-saml 策略配置

ts
import { Strategy as SAMLStrategy } from '@node-saml/passport-saml';

passport.use(new SAMLStrategy(
  {
    entryPoint: 'https://idp.example.com/SSOService.php',
    issuer: 'https://sp.example.com',
    callbackUrl: 'https://sp.example.com/acs',
    cert: fs.readFileSync('idp-signing-cert.pem', 'utf-8'), // ★ 预共享 IdP 签名证书
    signatureAlgorithm: 'sha256',                            // ★ 强制 SHA-256
    wantAssertionsSigned: true,                              // ★ 强制断言独立签名
    wantAuthnResponseSigned: true,                           // ★ 强制 Response 签名
    acceptedClockSkewMs: 60000,                              // 时钟偏差容忍(毫秒)
    disableRequestedAuthnContext: false,
    passReqToCallback: true,
  },
  (req, profile, done) => {
    // Assertion ID 去重
    const assertionId = profile.sessionIndex; // 字段以库版本为准
    return checkReplayCache(assertionId)
      .then(() => done(null, profile))
      .catch(done);
  },
));

XXE 防御(Node libxmljs2)

ts
import { parseXml } from 'libxmljs2';

// ★ 禁用 DTD 处理防 XXE
const doc = parseXml(samlResponseXml, {
  dtdload: false,    // 不加载外部 DTD
  dtdvalid: false,   // 不验证 DTD
  noent: false,      // ★ 不展开实体(防 XXE)
  nocdata: true,
});

攻击与防御对照表

攻击根因防御组合拳
XSW(XML Signature Wrapping)签名验证库与应用数据提取使用不同节点集wantAssertionsSigned=true + wantAuthnResponseSigned=true + 绝对 XPath + Reference URI 与业务节点一致 + 同一组件签名验证与数据提取
Replay同一有效断言被重复消费InResponseTo 关联 + Assertion ID 去重缓存 + 极短 NotOnOrAfter(典型 1 分钟)+ OneTimeUse
跨 SP 重放攻击者从 SP-A 截获断言提交给 SP-BDestination=ACS URL + Audience=SP EntityID + Recipient=ACS URL 三字段校验
XXEXML 解析器允许 DTD / 外部实体禁用 DTD 处理(noent=false 等)+ TLS 1.2+
Signature RemovalSP 只检查「有签名元素」但不实际验签实际跑两阶段签名验证
SHA-1 弱算法历史实现大量用 rsa-sha1强制 RSA-SHA-256 及以上,NIST 2030-12-31 前全面退役 SHA-1
KeyInfo 注入SP 信任文档内 <ds:KeyInfo> 选公钥StaticKeySelector / 从预共享 metadata 本地读取证书,忽略文档内 KeyInfo
Open Redirect(RelayState)SP 把 RelayState 当重定向目标但未 allowlistRelayState 若是 URL 须 allowlist 校验
TLS 降级通信未加密所有 SAML 通信强制 TLS 1.2+

OASIS 规范索引

规范全称用途
saml-core-2.0-osAssertions and Protocols for SAML 2.0核心规范(Assertion 结构、协议消息)
saml-bindings-2.0-osBindings for SAML 2.05 种绑定(Redirect/POST/Artifact/SOAP/PAOS)
saml-profiles-2.0-osProfiles for SAML 2.0Web Browser SSO、SSO、Single Logout、Name Identifier Management 等 Profile
saml-metadata-2.0-osMetadata for SAML 2.0元数据结构与签名
saml-authn-context-2.0-osAuthentication Context for SAML 2.0AuthnContextClassRef 详解
saml-tech-overview-2.0SAML 2.0 Technical Overview入门概览(最易读)
sstc-saml-approved-errata-2.0Approved Errata 05(2015)最新勘误

版本与生态

取值
核心规范OASIS SAML 2.0(2005-03,至今未废弃)
最新勘误Approved Errata 05(2015)
下一代主版本无 SAML 3.0(仅有 SAML-EOA、SAML-ECP 等扩展)
算法底线RSA-SHA-256(NIST SP 800-131A Rev.2)
SHA-1 退役2030-12-31 前(NIST)
Node 主流库tngan/samlify@node-saml/passport-samlnode-saml/xml-crypto
Java 主流库java-saml、OpenSAML
Python 主流库python3-saml、PySAML2
在线工具SAML Developer Tools(编解码、调试)
替代协议OpenID Connect(OIDC,2014)

官方资源