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

PHP实现带证书签名的WS-Security SOAP请求适配问题咨询

PHP WS-Security SOAP请求适配问题:robrichards/wse-php库与预期结构差异解决思路

我刚接触SOAP和WS-Security,现在需要用PHP发起带有WS-Security的SOAP请求,调研后选用了robrichards/wse-php库来添加安全头,但生成的SOAP信封和预期结构差异很大,不确定这个库是否能满足需求,也没找到合适的调整方法。

当前实现代码

require 'xmlseclibs-master/src/XMLSecurityKey.php';
require 'xmlseclibs-master/src/XMLSecurityDSig.php';
require 'xmlseclibs-master/src/XMLSecEnc.php';
require 'xmlseclibs-master/src/Utils/XPath.php';
require 'wse-php-master/src/WSSESoap.php';
require 'wse-php-master/src/WSASoap.php';
use RobRichards\WsePhp\WSASoap;
use RobRichards\WsePhp\WSSESoap;
use RobRichards\XMLSecLibs\XMLSecurityKey;

class mySoap extends SoapClient {
    private $cert = 'header.p7b';
    private $key = 'header.key';
    
    function __doRequest($request, $location, $saction, $version, $one_way = null) {
        $dom = new DOMDocument();
        $dom->loadXML($request);
        
        $objWSA = new WSASoap($dom);
        $objWSA->addAction($saction);
        $objWSA->addTo($location);
        $objWSA->addMessageID();
        $objWSA->addReplyTo();
        $dom = $objWSA->getDoc();
        
        $objWSSE = new WSSESoap($dom);
        /* Sign all headers to include signing the WS-Addressing headers */
        $objWSSE->signAllHeaders = TRUE;
        $objWSSE->addTimestamp(3600);
        
        /* create new XMLSec Key using RSA SHA-1 and type is private key */
        $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'private'));
        /* load the private key from file - last arg is bool if key in file (TRUE) or is string (FALSE) */
        $objKey->loadKey($this->key, TRUE);
        
        /* Sign the message - also signs appropraite WS-Security items */
        $objWSSE->signSoapDoc($objKey);
        
        /* Add certificate (BinarySecurityToken) to the message and attach pointer to Signature */
        $token = $objWSSE->addBinaryToken(file_get_contents($this->cert));
        $objWSSE->attachTokentoSig($token);
        
        $request = $objWSSE->saveXML();
        $f = fopen('debug.txt','w');
        fwrite($f,print_r($request,true));
        fclose($f);
        
        return parent::__doRequest($request, $location, $saction, $version);
    }
}

$opts = array(
    'ssl' => array(
        'local_cert' => 'file.pem',
        'local_pk' => 'file.key',
        #'passphrase' => 'password',
        'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
        'ciphers' => 'SHA256',
        'verify_peer'=>false,
        'verify_peer_name'=>false,
        'allow_self_signed' => true
    )
);

$soap = new mySoap('<wsdl>',array(
    'soap_version' => SOAP_1_1,
    'location' => '<location>',
    'trace' => 1,
    'exceptions' => 0,
    'stream_context' => stream_context_create($opts)
));

$array = array(
    "param1" => 'param1',
    "param2" => 'param2',
    "param3" => 'param3',
);

try {
    $out = $soap->method($array);
    var_dump($out);
} catch (SoapFault $fault) {
    var_dump($fault);
}

目标SOAP信封结构

<soap:Envelope xmlns:soap="##ENV_URL##">
    <soap:Header>
        <wsse:Security xmlns:wsse="##WSSE##" xmlns:wsu="##WSU_URL###" soap:mustUnderstand="1">
            <wsse:BinarySecurityToken EncodingType="##TOKEN_ENC##" ValueType="##TOKEN_VAL_TYPE##" wsu:Id="##TOKEN_ID##">##TOKEN_VAL##</wsse:BinarySecurityToken>
            <wsu:Timestamp wsu:Id="##URI1##">
                <wsu:Created>##DATE1##</wsu:Created>
                <wsu:Expires>##DATE2##</wsu:Expires>
            </wsu:Timestamp>
            <xenc:EncryptedKey xmlns:xenc="##ALG1_URL##" Id="##ALG1_ID##">
                <xenc:EncryptionMethod Algorithm="##ALG4_URL##"/>
                <ds:KeyInfo xmlns:ds="##KEY1_DS##">
                    <wsse:SecurityTokenReference>
                        <ds:X509Data>
                            <ds:X509IssuerSerial>
                                <ds:X509IssuerName>##ISSUER_NAME##</ds:X509IssuerName>
                                <ds:X509SerialNumber>##ISSUER_SERIAL##</ds:X509SerialNumber>
                            </ds:X509IssuerSerial>
                        </ds:X509Data>
                    </wsse:SecurityTokenReference>
                </ds:KeyInfo>
                <xenc:CipherData>
                    <xenc:CipherValue>##CIPHER1_VALUE##</xenc:CipherValue>
                </xenc:CipherData>
                <xenc:ReferenceList>
                    <xenc:DataReference URI="##ALG1_ID##"/>
                </xenc:ReferenceList>
            </xenc:EncryptedKey>
            <ds:Signature xmlns:ds="##KEY1_DS##" Id="##SIGN_ID##">
                <ds:SignedInfo>
                    <ds:CanonicalizationMethod Algorithm="##ALG2_URL##">
                        <ec:InclusiveNamespaces xmlns:ec="##ALG2_URL##" PrefixList="soap"/>
                    </ds:CanonicalizationMethod>
                    <ds:SignatureMethod Algorithm="##ALG4_URL##"/>
                    <ds:Reference URI="##URI1##">
                        <ds:Transforms>
                            <ds:Transform Algorithm="##ALG2_URL##">
                                <ec:InclusiveNamespaces xmlns:ec="##ALG2_URL##" PrefixList="soap wsse"/>
                            </ds:Transform>
                        </ds:Transforms>
                        <ds:DigestMethod Algorithm="##ALG5_URL##"/>
                        <ds:DigestValue>##DIG1_VALUE##</ds:DigestValue>
                    </ds:Reference>
                    <ds:Reference URI="##WSU_BODY_ID##">
                        <ds:Transforms>
                            <ds:Transform Algorithm="##ALG2_URL##"/>
                        </ds:Transforms>
                        <ds:DigestMethod Algorithm="##ALG5_URL##"/>
                        <ds:DigestValue>##DIG1_VALUE##</ds:DigestValue>
                    </ds:Reference>
                    <ds:Reference URI="###TOKEN_ID##">
                        <ds:Transforms>
                            <ds:Transform Algorithm="##ALG2_URL##">
                                <ec:InclusiveNamespaces xmlns:ec="##ALG2_URL##" PrefixList="soap"/>
                            </ds:Transform>
                        </ds:Transforms>
                        <ds:DigestMethod Algorithm="##ALG5_URL##"/>
                        <ds:DigestValue>##DIG2_VALUE##</ds:DigestValue>
                    </ds:Reference>
                </ds:SignedInfo>
                <ds:SignatureValue>##SIGN_VALUE##</ds:SignatureValue>
                <ds:KeyInfo Id="##KEY_ID##">
                    <wsse:SecurityTokenReference xmlns:wsse="##WSSE##" xmlns:wsu="##WSU_URL###" wsu:Id="##WSU_KEY_ID##">
                        <wsse:Reference URI="###TOKEN_ID##" ValueType="##TOKEN_VAL_TYPE##"/>
                    </wsse:SecurityTokenReference>
                </ds:KeyInfo>
            </ds:Signature>
        </wsse:Security>
    </soap:Header>
    <soap:Body xmlns:wsu="##WSU_URL###" wsu:Id="##WSU_BODY_ID##">
        <xenc:EncryptedData xmlns:xenc="##ALG1_URL##" Id="##ALG1_ID##" Type="##ALG1_URL##Content">
            <xenc:EncryptionMethod Algorithm="##ALG6_URL##"/>
            <ds:KeyInfo xmlns:ds="##KEY1_DS##">
                <wsse:SecurityTokenReference xmlns:wsse="##WSSE##" xmlns:wsse11="##WSSE11##" wsse11:TokenType="##WSSE11_TOKEN##">
                    <wsse:Reference URI="##ALG1_ID##"/>
                </wsse:SecurityTokenReference>
            </ds:KeyInfo>
            <xenc:CipherData>
                <xenc:CipherValue>##CYPHER2_VALUE##</xenc:CipherValue>
            </xenc:CipherData>
        </xenc:EncryptedData>
    </soap:Body>
</soap:Envelope>

适配解决方案建议

robrichards/wse-php库是可以支持你需要的WS-Security结构的,只是当前代码缺少Body加密、多节点签名引用等关键步骤,以下是具体调整方向:

  • 添加SOAP Body加密逻辑:
    目标结构里Body是加密状态,你需要使用XMLSecEnc类实现:

    1. 生成对称密钥(比如AES-256-CBC);
    2. 用对称密钥加密SOAP Body的内容;
    3. 用服务端的公钥加密这个对称密钥,生成<xenc:EncryptedKey>节点并插入到Security头中;
    4. 关联ReferenceList到加密的Body节点。
  • 调整签名的引用范围:
    当前代码只默认签名了部分节点,需要手动添加对Timestamp、BinarySecurityToken和Body的引用,确保签名覆盖这三个部分。可以在signSoapDoc之后,通过objWSSE->addReference方法添加额外的引用节点。

  • 完善BinarySecurityToken属性:
    调用addBinaryToken时传入属性参数,指定EncodingType、ValueType和wsu:Id:

    $token = $objWSSE->addBinaryToken(file_get_contents($this->cert), array(
        'EncodingType' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary',
        'ValueType' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3',
        'wsu:Id' => 'MyTokenId'
    ));
    
  • 修正Security头属性:
    手动为Security头添加soap:mustUnderstand="1"属性:

    $securityNode = $objWSSE->getSecurityHeader();
    $securityNode->setAttributeNS($objWSSE->soapNS, 'soap:mustUnderstand', '1');
    
  • 确保命名空间一致:
    检查目标结构中的命名空间URI(比如xenc是http://www.w3.org/2001/04/xmlenc#,ds是http://www.w3.org/2000/09/xmldsig#),确保库生成的节点使用相同的命名空间。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 18:17:44