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

如何用单PEM证书在NodeJS的node-soap中调用带SSL认证的SOAP API

问题:使用node-soap通过单PEM文件调用带SSL认证的SOAP服务

背景

已有可正常运行的PHP代码,通过单PEM文件实现SSL客户端认证并调用SOAP服务:

<?php

$certificate = 'demo/certificate.pem';
$location = '<serverurl>';
$passphrase = '<passphrase>';
$wsdl = 'demo/Api.wsdl';

$soapClient = new SoapClient(
    $wsdl,
    [
        'local_cert'     => $certificate,
        'location'       => $location,
        'passphrase'     => $passphrase,
        'stream_context' => stream_context_create([
            'http' => ['user_agent' => 'PHP/SOAP'],
            'ssl'  => [
                'verify_peer'      => false,
                'verify_peer_name' => false,
            ],
        ]),
    ]
);

// 调用SOAP方法
$result = $soapClient->Check([
    'checkReq' => [
        'operation' => 1,
        'voucherCode'  => 'abcd1234',
    ],
]);

print_r($result);

尝试用node-soap实现时,因库要求分开的私钥和证书文件,手动拆分PEM后出现解密错误:

const https = require("https")
const fs = require("fs")
const axios = require("axios")
const soap = require("soap")

class ApiWrapper {
    async getClient() {
        const privateKey = fs.readFileSync('./demo/private.pem', 'utf-8')
        const publicCertificate = fs.readFileSync('./demo/public.pem', 'utf-8')
        const password = '<passphrase>'

        const url = 'demo/Api.wsdl'

        const httpsAgent = new https.Agent({
            key: privateKey,
            cert: publicCertificate
        })

        const wsdlRequest = await axios.create({
            httpsAgent
        })

        let client = await soap.createClientAsync(
            url,
            {
                request: wsdlRequest,
                wsdl_options: {
                    connection: 'keep-alive'
                }
            }
        )

        const wsSecurity = new soap.WSSecurityCert(
            privateKey,
            publicCertificate,
            password
        )

        client.setSecurity(wsSecurity)
        return client
    }
}

async function main() {
    const wrapper = new ApiWrapper()
    const client = await wrapper.getClient()
    const result = await client.CheckAsync()
    console.log({result})
}

main()

错误信息

Error: error:1C800064:Provider routines::bad decrypt
    at Sign.sign (node:internal/crypto/sig:128:29)
    at RSASHA1.getSignature (/<path_to_project>/node_modules/xml-crypto/lib/signed-xml.js:70:22)
    at SignedXml.calculateSignatureValue (/<path_to_project>/node_modules/xml-crypto/lib/signed-xml.js:453:32)
    at SignedXml.computeSignature (/<path_to_project>/node_modules/xml-crypto/lib/signed-xml.js:865:10)
    at WSSecurityCert.postProcess (/<path_to_project>/node_modules/soap/lib/security/WSSecurityCert.js:141:21)
    at Client._invoke (/<path_to_project>/node_modules/soap/lib/client.js:357:33)
    at /<path_to_project>/node_modules/soap/lib/client.js:187:18
    at /<path_to_project>/node_modules/soap/lib/client.js:165:17
    at new Promise (<anonymous>)
    at Client.CheckAsync (/<path_to_project>/node_modules/soap/lib/client.js:156:20) {
  opensslErrorStack: [
    'error:11800074:PKCS12 routines::pkcs12 cipherfinal error',
    'error:1C800064:Provider routines::bad decrypt',
    'error:11800074:PKCS12 routines::pkcs12 cipherfinal error'
  ],
  library: 'Provider routines',
  reason: 'bad decrypt',
  code: 'ERR_OSSL_BAD_DECRYPT'
}

解决方案

核心问题:混淆了SSL客户端认证与WS-Security签名

PHP代码中使用的是SSL层面的客户端证书认证(通过local_cert配置),而node-soap的WSSecurityCert是用于WS-Security协议的XML内容签名,二者完全不同。正确的做法是使用ClientSSLSecurity实现SSL客户端认证,无需手动拆分PEM文件。

完整实现代码

const fs = require('fs');
const soap = require('soap');
const https = require('https');
const axios = require('axios');

class ApiWrapper {
    async getClient() {
        const pemPath = './demo/certificate.pem';
        const passphrase = '<passphrase>';
        const wsdlUrl = 'demo/Api.wsdl';
        const serverUrl = '<serverurl>';

        // 读取单PEM文件内容
        const pemContent = fs.readFileSync(pemPath, 'utf-8');

        // 从PEM中提取私钥和证书(兼容RSA PRIVATE KEY和PRIVATE KEY格式)
        const privateKey = pemContent.match(/-----BEGIN (?:RSA )?PRIVATE KEY-----[\s\S]+?-----END (?:RSA )?PRIVATE KEY-----/)[0];
        const publicCert = pemContent.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/)[0];

        // 配置HTTPS Agent,对应PHP中的stream_context
        const httpsAgent = new https.Agent({
            key: privateKey,
            cert: publicCert,
            passphrase: passphrase,
            // 对应PHP的verify_peer=false,生产环境建议开启并配置可信CA
            rejectUnauthorized: false
        });

        // 创建带HTTPS Agent的请求实例,用于WSDL获取和SOAP调用
        const axiosInstance = axios.create({ httpsAgent });

        // 创建SOAP客户端
        const client = await soap.createClientAsync(wsdlUrl, {
            request: axiosInstance,
            wsdl_options: { connection: 'keep-alive' },
            // 指定SOAP请求的目标地址,对应PHP的location参数
            endpoint: serverUrl
        });

        // 使用ClientSSLSecurity实现SSL客户端认证,与PHP逻辑一致
        const sslSecurity = new soap.ClientSSLSecurity(
            privateKey,
            publicCert,
            {
                passphrase: passphrase,
                rejectUnauthorized: false
            }
        );
        client.setSecurity(sslSecurity);

        return client;
    }
}

async function main() {
    try {
        const wrapper = new ApiWrapper();
        const client = await wrapper.getClient();
        // 传递与PHP相同的参数
        const [result] = await client.CheckAsync({
            checkReq: {
                operation: 1,
                voucherCode: 'abcd1234'
            }
        });
        console.log('SOAP响应:', result);
    } catch (error) {
        console.error('调用失败:', error);
    }
}

main();

关键说明

  1. PEM内容提取:通过正则从单PEM文件中自动拆分私钥和证书,避免手动拆分时的格式错误。
  2. 正确选择安全类:使用ClientSSLSecurity而非WSSecurityCert,匹配PHP的SSL客户端认证逻辑。
  3. 参数对应:
    • rejectUnauthorized: false对应PHP的verify_peer: false和verify_peer_name: false
    • endpoint参数对应PHP的location
    • 调用CheckAsync时需传递与PHP一致的请求参数

注意事项

  • 生产环境中请勿关闭rejectUnauthorized,应配置可信CA证书以保障安全。
  • 若服务端确实需要WS-Security签名,需确保私钥和证书提取正确,且密码无误,再使用WSSecurityCert配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 05:42:02