SAML响应的XML签名怎么验证

11次阅读

SAML签名验证必须先对SignedInfo节点做排他性规范化(exclusive canonicalization),再用公钥验签;直接解Base64后验整个xml字节必然失败,因签名仅覆盖规范化后的SignedInfo子树。

SAML响应的XML签名怎么验证

Signature 验证不是解 Base64 后直接验字节 —— 你必须先对 SignedInfo排他性规范化(exclusive canonicalization),再用公钥验签。否则哪怕 XML 结构完全正确,sig.verify() 也一定失败。


为什么直接验 contentBytes 总是 false?

你当前的 verifySignature 方法传入的是整个响应 XML 的字节,但 SAML 签名只覆盖 SignedInfo 节点(含其子节点),且该节点在签名前已被按特定规则标准化(比如去掉无关空白、归一化命名空间声明)。直接传原始 XML 字符串或未规范化的节点内容,哈希值必然不匹配。

  • SignedInfo 是签名的“输入原文”,不是整个 Response
  • 规范化算法ds:CanonicalizationMethod@Algorithm 指定,常见为 http://www.w3.org/2001/10/xml-exc-c14n#
  • java 原生 node.getC14NMethod() 不支持自动识别该算法,需手动调用 org.jcp.xml.dsig.internal.dom.DOMCanonicalizer 或用 xmlsec

xmlsec 库最稳(推荐)

apache xmlsec 是 Java 生态验证 SAML 签名的事实标准,它自动处理:CanonicalizationMethod 解析、Reference URI 解析(如 #_response123456)、Transforms(如 enveloped-signature 剔除签名自身)、以及 SignatureMethod 映射(rsa-sha256 / rsa-sha1 自动适配)。

import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.transforms.Transforms; import org.w3c.dom.Document; import org.w3c.dom.Element; 

// 已加载 Document doc Element sigElement = (Element) doc.getElementsByTagNameNS("https://www.php.cn/link/573a77b45da4e86a0fc93e5f76cc99ce#", "Signature").item(0); XMLSignature signature = new XMLSignature(sigElement, ""); boolean isValid = signature.checkSignatureValue(publicKey); // 自动做 c14n + verify

  • 别自己解析 ds:SignatureValueds:SignedInfo —— xmlsec 内部已封装全部逻辑
  • 确保 classpath 有 xmlsec-3.0.3.jar(适配 Java 11+)和 xmlsec-java-3.0.3.jar
  • 若用 Keycloak 发出的响应,注意其 SignatureMethod 很可能是 rsa-sha256,旧版 xmlsec(如 2.x)默认不支持,必须升到 3.x

手撸验证时,三个关键点不能漏

如果因合规或轻量要求必须不用第三方库,务必确认以下三步都严格对齐 SAML 响应中的 ds:Signature 描述:

  • 取对节点:用 XPath 定位到 ds:SignedInfo 元素,不是 ds:Signature 根节点
  • 做对规范化:调用 signedInfoNode.getC14NMethod().canonicalizeSubtree(signedInfoNode),且参数必须设为 true(保留注释)或 false(剔除注释),取决于 CanonicalizationMethod@Algorithm 是否带 #WithComments
  • 验对算法:从 ds:SignatureMethod@Algorithm 提取实际算法(如 http://www.w3.org/2001/04/xmldsig-more#rsa-sha256),映射为 Java 支持的字符串(SHA256withRSA),而非硬写 RSA-SHA1

SAML 签名验证真正难的不是代码,而是对 ds:ReferenceURITransformsDigestMethod 这三者的联动理解 —— 它们共同定义了“到底哪段字节被签了”,漏掉任意一个环节,就等于在验一份假数据。

text=ZqhQzanResources