
本文介绍在 node.js 中实现 soap 请求多重数字签名及加密的实用方案,涵盖手动构建签名流程、使用 crypto 模块定制化处理、替代库推荐,并提供可运行的代码示例与关键注意事项。
本文介绍在 node.js 中实现 soap 请求多重数字签名及加密的实用方案,涵盖手动构建签名流程、使用 crypto 模块定制化处理、替代库推荐,并提供可运行的代码示例与关键注意事项。
在企业级集成场景中,某些符合 WS-Security 规范的 SOAP 服务(如银行、政务或医疗系统)要求请求消息包含多个独立的 XMLDSig 签名(例如:分别对 、<timestamp></timestamp> 和自定义 <header></header> 元素签名),甚至需在签名后对部分敏感内容进行 WSS-Encryption 加密。而主流的 node-soap 库仅支持单次 WSSecurityCert 安全配置,其 setSecurity() 调用会覆盖前一次设置,无法原生满足“嵌套签名+加密”的复合安全需求。
要实现真正的多重签名,核心思路是脱离自动 XML 构建流程,转为手动控制 SOAP 消息生命周期:先生成原始 SOAP XML → 使用标准 XML 签名库逐层签名(每次更新 <signature></signature> 并插入对应 <reference></reference>)→ 最终对指定元素加密。以下是分步实践方案:
✅ 方案一:基于 xml-crypto 手动多层签名(推荐)
xml-crypto 是目前 Node.js 生态中最成熟、符合 W3C XMLDSig 标准的签名库,支持多次签名、引用解析和规范化(Canonicalization)。
npm install xml-crypto xml2js xmlbuilder2
const fs = require('fs');
const { SignedXml } = require('xml-crypto');
const { parseStringPromise, Builder } = require('xml2js');
const { create } = require('xmlbuilder2');
// 1. 构建原始 SOAP Envelope(含 Timestamp 和 Body)
const soapEnvelope = create({
version: '1.0',
encoding: 'UTF-8'
}).ele('soap:Envelope', {
'xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/',
'xmlns:wsu': 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
})
.ele('soap:Header')
.ele('wsu:Timestamp', { 'wsu:Id': 'TS-1' })
.ele('wsu:Created').txt(new Date().toISOString()).up()
.ele('wsu:Expires').txt(new Date(Date.now() + 300000).toISOString())
.up()
.up()
.ele('soap:Body')
.ele('MyOperation', { 'xmlns': 'http://example.com/' })
.ele('param').txt('value').up()
.up()
.up()
.end({ prettyPrint: true });
console.log('原始 SOAP:\n', soapEnvelope);
// 2. 第一次签名:对 Timestamp 元素
const signedXml1 = new SignedXml();
signedXml1.addReference("//*[local-name(.)='Timestamp']", ['http://www.w3.org/2001/10/xml-exc-c14n#']);
signedXml1.signingKey = fs.readFileSync('./private-key.pem');
signedXml1.keyInfoProvider = {
getKeyInfo: () => '<securitytokenreference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><reference uri="#TS-1"></reference></securitytokenreference>'
};
signedXml1.computeSignature(soapEnvelope);
let signedEnvelope = signedXml1.getSignedXml();
// 3. 第二次签名:对 Body 元素(需确保新签名不破坏已有签名)
const signedXml2 = new SignedXml();
signedXml2.addReference("//*[local-name(.)='Body']", ['http://www.w3.org/2001/10/xml-exc-c14n#']);
signedXml2.signingKey = fs.readFileSync('./private-key-2.pem'); // 可使用不同密钥
signedXml2.keyInfoProvider = {
getKeyInfo: () => '<keyidentifier xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" valuetype="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3">...</keyidentifier>'
};
signedXml2.computeSignature(signedEnvelope);
signedEnvelope = signedXml2.getSignedXml();
console.log('双重签名后的 SOAP:\n', signedEnvelope);
⚠️ 关键注意事项:
- 每次签名必须指定唯一
wsu:Id,且<reference uri="#xxx"></reference>必须严格匹配;- 使用
xml-crypto时务必启用http://www.w3.org/2001/10/xml-exc-c14n#规范化,避免因空白符或命名空间差异导致验签失败;- 多重签名后 XML 体积显著增大,需确认服务端支持长消息(建议设置
client.setEndpoint(url)后调用client.setSOAPAction(...)并检查 HTTPContent-Length);- 若需加密,可在最终签名完成后,用
xml-encryption库对或<signature></signature>内容 AES 加密,并注入<encrypteddata></encrypteddata>。
✅ 方案二:替代库与进阶工具链
-
strong-soap:node-soap的活跃分支,支持插件式 SecurityProvider,可通过自定义Security类实现签名链; -
ws-security:轻量级 WS-Security 工具包,支持UsernameToken、X509Token和基础Signature,适合简单多签组合; - 生产级推荐:结合 Apache WSS4J(Java)通过 gRPC 或 HTTP bridge 调用,利用其完整 WS-* 栈能力,Node.js 侧专注业务逻辑。
✅ 总结
node-soap 的设计目标是简化标准 SOAP 调用,而非覆盖全部 WS-Security 复杂场景。当遇到多重签名、动态加密、SAML 断言嵌入等需求时,应主动放弃“全自动”封装,转向以 xml-crypto + xml2js 为核心的可控 XML 构建流水线。该方式虽增加开发成本,但能精准控制每个 <signature></signature> 的作用域、算法(RSA-SHA256)、密钥引用方式及加密策略,是保障企业互操作性与合规性的可靠路径。











