You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何验证Azure AD生成的SAML Response签名?Node.js实现遇阻

Azure AD SAML Response签名验证失败问题排查

问题描述

我需要验证Azure AD生成的SAML Response,已在Azure AD SSO设置中将“SAML Signing Option”配置为“SAML Response”。原始响应经Base64解码后是无空格的XML。我从XML中提取了SignatureValue和X509Certificate,使用Node.js的crypto模块编写了验证代码,但运行后始终返回“SAML Response signature is invalid”,想知道遗漏了哪些步骤。

现有验证代码

const fs = require('fs');
const crypto = require('crypto');

const samlResponse  = '<samlp:Response...>...</samlp:Response>' //完整XML字符串
const signatureValue = '...' //<SignatureValue>标签内的值
const x509Certificate = '...' //<ds:X509Certificate>标签内的值
const cert = `-----BEGIN CERTIFICATE-----
${x509Certificate}
-----END CERTIFICATE-----`;

// 验证签名
const verifier = crypto.createVerify('RSA-SHA256');
verifier.update(samlResponse, 'utf8');

const isSignatureValid = verifier.verify(cert, signatureValue, 'base64');

if (isSignatureValid) {
    console.log('SAML Response signature is valid.');
} else {
    console.error('SAML Response signature is invalid.');
}

SAML Response结构

<samlp:Response ID="_188301aa-efa1-405f-ad6d-cf0ae65e347b" Version="2.0" IssueInstant="2023-09-27T18:28:02.720Z" Destination="some destination..."
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
    <Issuer
        xmlns="urn:oasis:names:tc:SAML:2.0:assertion">some...
    </Issuer>
    <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="#_188301aa-efa1-405f-ad6d-cf0ae65e347b">
                <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>...</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>...</SignatureValue>
        <KeyInfo>
            <ds:X509Data
                xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Certificate>...</ds:X509Certificate>
            </ds:X509Data>
        </KeyInfo>
    </Signature>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <Assertion ID="_bcc97cde-fe0d-4f41-92df-4a32348c1c00" IssueInstant="2023-09-27T18:28:02.719Z" Version="2.0"
        xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
        <Issuer>...</Issuer>
        <Subject>
            <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">...</NameID>
            <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <SubjectConfirmationData NotOnOrAfter="2023-09-27T19:28:02.519Z" Recipient="..."/>
            </SubjectConfirmation>
        </Subject>
        <Conditions NotBefore="2023-09-27T18:23:02.519Z" NotOnOrAfter="2023-09-27T19:28:02.519Z">
            <AudienceRestriction>
                <Audience>matansso</Audience>
            </AudienceRestriction>
        </Conditions>
        <AttributeStatement>
            ...
        </AttributeStatement>
        <AuthnStatement AuthnInstant="2023-09-19T13:39:36.939Z" SessionIndex="_bcc97cde-fe0d-4f41-92df-4a32348c1c00">
            <AuthnContext>
                <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef>
            </AuthnContext>
        </AuthnStatement>
    </Assertion>
</samlp:Response>

遗漏的关键步骤

1. 未遵循XML-DSig的转换与规范化规则

你的代码直接使用原始XML字符串进行验证,但根据SAML Response中的<SignedInfo>配置,签名计算前必须执行两个核心步骤:

  • 信封式签名转换:移除整个<Signature>节点(签名节点本身不参与签名计算)
  • 独占XML规范化(Exc-C14N):对移除签名节点后的XML执行严格规范化,包括命名空间排序、属性排序、空白字符处理等,必须完全符合http://www.w3.org/2001/10/xml-exc-c14n#标准

手动实现这些规则极易出错,比如命名空间上下文保留、特殊字符处理等,这是验证失败的核心原因。

2. 直接使用crypto模块的局限性

Node.js的crypto模块仅支持原始字符串的签名验证,无法处理XML-DSig特有的转换和规范化逻辑,必须使用专门的XML签名库来处理这些步骤。

修正后的验证代码示例(使用xml-crypto)

const xmlCrypto = require('xml-crypto');

const samlResponse = '<samlp:Response...>...</samlp:Response>'; // 完整XML字符串
const x509Certificate = '...'; // <ds:X509Certificate>标签内的值
const responseId = '_188301aa-efa1-405f-ad6d-cf0ae65e347b'; // 对应Response的ID属性

// 初始化签名验证器
const sig = new xmlCrypto.SignedXml();
// 配置签名引用规则,与SAML中的<Reference>配置完全匹配
sig.addReference(
  `//*[@ID='${responseId}']`,
  ["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/2001/10/xml-exc-c14n#"],
  "http://www.w3.org/2001/04/xmlenc#sha256"
);
// 设置验证用的公钥证书
sig.signingKey = `-----BEGIN CERTIFICATE-----
${x509Certificate}
-----END CERTIFICATE-----`;

// 加载签名并验证
sig.loadSignature(samlResponse);
const isValid = sig.checkSignature(samlResponse);

if (isValid) {
  console.log('SAML Response签名验证通过');
} else {
  console.error('SAML Response签名验证失败,错误信息:', sig.validationErrors);
}

额外注意事项

  • 确保解码后的XML未被修改(如添加换行、空格),保持原始无空格状态
  • 确认X509Certificate完整无截断,拼接PEM格式时每行64字符是标准要求(部分库会自动处理,但建议遵循)
  • 验证时需同时检查SAML的其他属性,比如Destination、NotBefore/NotOnOrAfter等,确保响应合法性

内容的提问来源于stack exchange,提问作者matan__2

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 03:37:02