PHP实现带证书签名的WS-Security SOAP请求适配问题咨询
我刚接触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类实现:- 生成对称密钥(比如AES-256-CBC);
- 用对称密钥加密SOAP Body的内容;
- 用服务端的公钥加密这个对称密钥,生成
<xenc:EncryptedKey>节点并插入到Security头中; - 关联
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

