带有WCF BadContextToken的PHP Soap客户端

时间:2014-01-19 15:47:57

标签: php wcf soap ws-security wshttpbinding

经过几天google -ing /尝试/失去头发后,我仍然无法找到解决方案,所以请帮助:)

简短信息:
我需要使用PHP(SOAP客户端)的WCF服务。它使用wsHttpBinding(ws-security),无法设置basicHttpBinding。一切都在VPN背后,所以我无法为您提供webservice的链接。此外,数据被视为机密(来自客户的请求),因此我无法向您提供完整信息,只能提供一些“常见”信息。这是WS config:

<configuration>
<system.serviceModel>
    <bindings>
        <wsHttpBinding>
            <binding name="WSHttpBinding_IServices" closeTimeout="00:01:00"
                openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
                maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
                <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" />
                <security mode="TransportWithMessageCredential">
                    <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
                    <message clientCredentialType="UserName" negotiateServiceCredential="true" algorithmSuite="Default" />
                </security>
            </binding>
        </wsHttpBinding>
    </bindings>
    <client>
        <endpoint address="https://topSecert.url/Service.svc"
            binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IServices"
            contract="IServices" name="WSHttpBinding_IServices" />
    </client>
</system.serviceModel>

我的尝试:
1)基本的PHP Soap客户端不起作用。它始终挂起,直到达到最大执行时间(不生成错误)。我后来发现PHP Soap客户端不支持wsHttpBinding(想哭) 2)一些SoapClient扩展classes但没有成功,请求仍然挂起。
3)使用SOAPAction头尝试“自生成”CURL请求。最后我得到了一些错误(我用wse类生成了请求):

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
    <a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>
</s:Header>
<s:Body>
    <s:Fault>
        <s:Code>
            <s:Value>s:Sender</s:Value>
            <s:Subcode>
                <s:Value xmlns:a="http://schemas.xmlsoap.org/ws/2005/02/sc">a:BadContextToken</s:Value>
            </s:Subcode>
        </s:Code>
        <s:Reason>
            <s:Text xml:lang="en-US">The security context token is expired or is
                not valid. The message was not processed.</s:Text>
        </s:Reason>
    </s:Fault>
</s:Body>

我将服务器时间更改为有效区域(与WCF相同),尝试使用nonce,哈希密码,普通密码以及其他一些我现在都记不起来的东西。 我也尝试编译wso2/wsf但是无法在PHP 5.4上编译它(我尝试应用提供的FIX但是它导致了同样的错误)。

测试XML示例:

<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns1="https://topSercret.url/Test">
<env:Header>
    <wsse:Security
        xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
        env:mustUnderstand="1">
        <wsse:UsernameToken>
            <wsse:Username><!-- Removed --></wsse:Username>
            <wsse:Password
                Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"><!-- Removed --></wsse:Password>
            <wsse:Nonce><!-- Removed --></wsse:Nonce>
            <wsu:Created
                xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2014-01-19T15:20:31Z</wsu:Created>
        </wsse:UsernameToken>
        <wsu:Timestamp
            xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
            <wsu:Created>2014-01-19T15:20:31Z</wsu:Created>
            <wsu:Expires>2014-01-19T16:20:31Z</wsu:Expires>
        </wsu:Timestamp>
    </wsse:Security>
</env:Header>
<env:Body>
    <ns1:SomeAction />
</env:Body>

这是测试脚本的代码(可能有错误,我为此帖子删除了大部分内容):

<?php

date_default_timezone_set( 'UTC' );

include 'WSSESoap.php';

class TestSoap extends SoapClient {

    private $_username;
    private $_password;
    private $_digest;

    // test vars
    public $r_request;
    public $r_location;
    public $r_action;

    function addUserToken($username, $password, $digest = false) {
        $this->_username = $username;
        $this->_password = $password;
        $this->_digest = $digest;
    }

    function __doRequest($request, $location, $saction, $version, $one_way = 0) {
        $doc = new DOMDocument('1.0');
        $doc->loadXML($request);

        $objWSSE = new WSSESoap($doc);
        $objWSSE->signAllHeaders = TRUE;

        $objWSSE->addTimestamp();
        $objWSSE->addUserToken($this->_username, $this->_password, $this->_digest);

        // take data for "my" usage
        $this->r_request = $objWSSE->saveXML();
        $this->r_location = $location;
        $this->r_action = $saction;
        return '';
    }
}

function test()
{
    $soapUrl = "https://topSecret.url/Services.svc";

    $context = stream_context_create(array(
            'ssl' => array(
                    'verify_peer' => false,
                    'allow_self_signed' => true
            )
    ));

    $client  = new TestSoap('/mypath/wsdl.xml', array(
            'stream_context' => $context,
            'soap_version' => SOAP_1_2,
            'trace' => 1,
            'connection_timeout' => 10
    ));
    $client->addUserToken('User', 'Password', TRUE );

    $requestParams = array(
            'data1' => '1',
            'data2' => '2',
    );

    // call to generate request string
    $client->myAction($requestParams);
    $xml_post_string = $client->r_request;

    $headers = array(
            "Content-type: application/soap+xml; charset=\"utf-8\"",
            "Accept: text/xml,application/soap+xml",
            "Cache-Control: no-cache",
            "Pragma: no-cache",
            "SOAPAction: " . $client->r_action,
            "Content-length: " . strlen($xml_post_string)
    );

    // generate && run cURL request
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_URL, $soapUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_post_string); // the SOAP request
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    echo $response;
}

test();

所以最后这个问题。可以使用PHP来使用这种服务(如果它可以帮助理解如何)?

1 个答案:

答案 0 :(得分:9)

我前段时间解决了这个问题,但从来没有时间让它更“更好”。因此,问题通常是wsHttpBinding消息安全如何工作以及如何在PHP上实现它的方式。我使用了来自https://github.com/enginaygen/kps-soap-client/blob/master/KPSSoapClient.php的概念,还添加了来自Implementation of P_SHA1 algorithm in PHP的psha1。

所以它需要工作的方式是:

  1. 来自WSS的PHP安全令牌请求及其请求密码
  2. WS生成安全令牌并将其返回给PHP
  3. PHP生成带有请求安全令牌的SOAP C14N签名消息
  4. 这是imeplentation(注意:由于WSDL导入问题,我没有通过扩展PHP soap客户端来实现它。另外正如我所说的,我使用了其他人的概念,从未做过清理代码 - 尤其是XML生成)。

    // TODO implement this by extending SoapClient class
    // currently not implemented in it because request params are not generated correctly
    
    /**
     * Client implementing SOAP wsHttpBinding with message security. <br>
     * NOTE: this is adapted to work for special needs of our client. It can be modified and there is a lot of work that jet needs to be done (nicer code, options and optimization).
     */
    
    class WSSoap
    {
        /**
         * Securit token request template
         */
        const STS_TEMPLATE = <<<X
    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT</a:Action><a:MessageID></a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand="1"></a:To><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><u:Timestamp u:Id="_0"><u:Created></u:Created><u:Expires></u:Expires></u:Timestamp><o:UsernameToken u:Id="_1"><o:Username></o:Username><o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"></o:Password></o:UsernameToken></o:Security></s:Header><s:Body><t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust"><t:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</t:TokenType><t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType><t:Entropy><t:BinarySecret Type="http://schemas.xmlsoap.org/ws/2005/02/trust/Nonce"></t:BinarySecret></t:Entropy><t:KeySize>256</t:KeySize></t:RequestSecurityToken></s:Body></s:Envelope>
    X;
    
        /**
         * Any action request template (mainly for headers)
         */
        const KPS_TEMPLATE = <<<X
    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><a:Action s:mustUnderstand="1">n</a:Action><a:MessageID></a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand="1"></a:To><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><u:Timestamp u:Id="_0"><u:Created></u:Created><u:Expires></u:Expires></u:Timestamp><c:SecurityContextToken xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc"><c:Identifier></c:Identifier></c:SecurityContextToken><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"></SignatureMethod><Reference URI="#_0"> <Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue></DigestValue></Reference></SignedInfo><SignatureValue></SignatureValue><KeyInfo><o:SecurityTokenReference><o:Reference ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct"></o:Reference></o:SecurityTokenReference></KeyInfo></Signature></o:Security></s:Header><s:Body></s:Body></s:Envelope>
    X;
    
        /**
         * Namespaces
         */
        const S11 = "http://schemas.xmlsoap.org/soap/envelope/";
        const S12 = "http://www.w3.org/2003/05/soap-envelope";
        const WSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
        const WSSE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
        const WSSE11 = "http://docs.oasis-open.org/wss/oasis-wss-wsecurity-secext-1.1.xsd";
        const WST = "http://schemas.xmlsoap.org/ws/2005/02/trust";
        const DS = "http://www.w3.org/2000/09/xmldsig#";
        const XENC = "http://www.w3.org/2001/04/xmlenc#";
        const WSP = "http://schemas.xmlsoap.org/ws/2004/09/policy";
        const WSA = "http://www.w3.org/2005/08/addressing";
        const XS = "http://www.w3.org/2001/XMLSchema";
        const WSDL = "http://schemas.xmlsoap.org/wsdl/";
        const SP = "http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702";
        const SC = "http://schemas.xmlsoap.org/ws/2005/02/sc";
    
        /**
         * STS Properties
         */
        protected $stsHostName;
        protected $stsEndpoint;
        protected $stsUsername;
        protected $stsPassword;
        protected $stsNamespace;
    
        /**
         * Binary secret used for generating request
         */
        protected $requestSecret;
        protected $rstrBinarySecret;
        protected $rstrKeyIdentifier;
    
        protected $token;
        protected $tokenReference;
    
        function __construct( $username, $password, $endpointURL, $namespace )
        {
            $this->stsUsername = $username;
            $this->stsPassword = $password;
            $this->stsHostName = parse_url( $endpointURL, PHP_URL_HOST);
            $this->stsEndpoint = $endpointURL;
            $this->stsNamespace = $namespace;
        }
    
    
    
        function request( $action, $fullActionName, $params )
        {
            $this->stsRequest();
    
            $kpsDom = new \DOMDocument("1.0", "utf-8");
            $kpsDom->preserveWhiteSpace = false;
            $kpsDom->loadXML(static::KPS_TEMPLATE);
    
            $kpsXpath = new \DOMXPath($kpsDom);
            $kpsXpath->registerNamespace('S12', static::S12);
            $kpsXpath->registerNamespace('WSA', static::WSA);
            $kpsXpath->registerNamespace('WSU', static::WSU);
            $kpsXpath->registerNamespace('WSSE', static::WSSE);
            $kpsXpath->registerNamespace('XENC', static::XENC);
            $kpsXpath->registerNamespace('DS', static::DS);
            $kpsXpath->registerNamespace('SC', static::SC);
    
            // Addressing
    
            $uuid = $this->uuid();
    
            $actionPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSA:Action");
            $messageIDPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSA:MessageID");
            $toPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSA:To");
    
            $actionPath->item(0)->nodeValue = $fullActionName;
            $messageIDPath->item(0)->nodeValue = sprintf("urn:uuid:%s", $uuid);
            $toPath->item(0)->nodeValue = $this->stsEndpoint;
    
            // Timestamp
    
            $time = time();
    
            $dateCreated = gmdate('Y-m-d\TH:i:s\Z', $time);
            $dateExpires = gmdate('Y-m-d\TH:i:s\Z', $time + (5 * 60));
    
            $timestampPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSU:Timestamp");
            $timestampDateCreatedPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSU:Timestamp/WSU:Created");
            $timestampDateExpiresPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSU:Timestamp/WSU:Expires");
            $timestampDateCreatedPath->item(0)->nodeValue = $dateCreated;
            $timestampDateExpiresPath->item(0)->nodeValue = $dateExpires;
            $timestampC14N = $timestampPath->item(0)->C14N(true, false);
    
            // DigestValue
            $digestValue = base64_encode(hash('sha1', $timestampC14N, true));
            $digestValuePath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/DS:Signature/DS:SignedInfo/DS:Reference/DS:DigestValue");
            $digestValuePath->item(0)->nodeValue = $digestValue;
    
            // Signature
            $signaturePath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/DS:Signature/DS:SignedInfo");
            $signatureValuePath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/DS:Signature/DS:SignatureValue");
            $signatureC14N = $signaturePath->item(0)->C14N(true, false);
    
            $psBinary = $this->psha1( $this->requestSecret, $this->rstrBinarySecret );
            $signatureValue = base64_encode(hash_hmac("sha1", $signatureC14N, $psBinary, true));
            $signatureValuePath->item(0)->nodeValue = $signatureValue;
    
            // token reference
            $securityContextTokenReference = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/DS:Signature/DS:KeyInfo/WSSE:SecurityTokenReference/WSSE:Reference");
            $securityContextTokenReference->item(0)->setAttribute('URI', "#$this->tokenReference");
            // token ID
            $tokenPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/SC:SecurityContextToken");
            $tokenPath->item(0)->setAttribute('u:Id', $this->tokenReference);
            // token
            $tokenPath = $kpsXpath->query("//S12:Envelope/S12:Header/WSSE:Security/SC:SecurityContextToken/SC:Identifier");
            $tokenPath->item(0)->nodeValue = $this->token;
    
            // Message
            $bodyElemet = $kpsXpath->query("//S12:Envelope/S12:Body")->item(0);
            $root = $kpsDom->createElementNS( $this->stsNamespace, $action );
    
            foreach( $params as $name => $value ) {
                $root->appendChild( $kpsDom->createElement( $name, $value ) );
            }
    
            $bodyElemet->appendChild( $root );
            $kpsRequest = $kpsDom->saveXML();
    
            // Request
            try {
                $stsResponse = $this->execCurl( $kpsRequest );
            } catch ( \Exception $e ) {
                throw $e;
            }
    
            return $stsResponse;
        }
    
    
        /**
         * Performs a STS request
         *
         * @param string $location Request location
         */
        protected function stsRequest()
        {
            $rstXml = static::STS_TEMPLATE;
    
            $rstDom = new \DOMDocument("1.0", "utf-8");
            $rstDom->preserveWhiteSpace = false;
            $rstDom->loadXML($rstXml);
    
            $rstXpath = new \DOMXPath($rstDom);
            $rstXpath->registerNamespace('S12', static::S12);
            $rstXpath->registerNamespace('WSA', static::WSA);
            $rstXpath->registerNamespace('WSU', static::WSU);
            $rstXpath->registerNamespace('WSSE', static::WSSE);
            $rstXpath->registerNamespace('XENC', static::XENC);
            $rstXpath->registerNamespace('DS', static::DS);
            $rstXpath->registerNamespace('WST', static::WST);
            $rstXpath->registerNamespace('WSP', static::WSP);
    
            // Addressing
    
            $uuid = $this->uuid();
    
            $messageIDPath = $rstXpath->query("//S12:Envelope/S12:Header/WSA:MessageID");
            $toPath = $rstXpath->query("//S12:Envelope/S12:Header/WSA:To");
    
            $messageIDPath->item(0)->nodeValue = sprintf("urn:uuid:%s", $uuid);
            $toPath->item(0)->nodeValue = $this->stsEndpoint;
    
            // Timestamp
    
            $time = time();
    
            $dateCreated = gmdate('Y-m-d\TH:i:s\Z', $time);
            $dateExpires = gmdate('Y-m-d\TH:i:s\Z', $time + (5 * 60));
    
            $timestampDateCreatedPath = $rstXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSU:Timestamp/WSU:Created");
            $timestampDateExpiresPath = $rstXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSU:Timestamp/WSU:Expires");
            $timestampDateCreatedPath->item(0)->nodeValue = $dateCreated;
            $timestampDateExpiresPath->item(0)->nodeValue = $dateExpires;
    
            // Credentials
    
            $usernamePath = $rstXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSSE:UsernameToken/WSSE:Username");
            $passwordPath = $rstXpath->query("//S12:Envelope/S12:Header/WSSE:Security/WSSE:UsernameToken/WSSE:Password");
    
            $usernamePath->item(0)->nodeValue = $this->stsUsername;
            $passwordPath->item(0)->nodeValue = $this->stsPassword;
    
            // Set binary key
            $this->requestSecret = uniqid();
            $binaryKeyPath = $rstXpath->query("//S12:Envelope/S12:Body/WST:RequestSecurityToken/WST:Entropy/WST:BinarySecret");
            $binaryKeyPath->item(0)->nodeValue = base64_encode( $this->requestSecret );
    
            // Endpoint
            $stsRequest = $rstDom->saveXML();
    
            // Request
            try {
                $stsResponse = $this->execCurl( $stsRequest );
            } catch ( \Exception $e ) {
                throw $e;
            }
    
            $rstrDom = new \DOMDocument("1.0", "utf-8");
            $rstrDom->preserveWhiteSpace = false;
            $rstrDom->loadXML($stsResponse);
    
            $rstrXpath = new \DOMXPath($rstrDom);
    
            $rstrXpath->registerNamespace('S12', static::S12);
            $rstrXpath->registerNamespace('WSA', static::WSA);
            $rstrXpath->registerNamespace('WSU', static::WSU);
            $rstrXpath->registerNamespace('WSSE', static::WSSE);
            $rstrXpath->registerNamespace('XENC', static::XENC);
            $rstrXpath->registerNamespace('DS', static::DS);
            $rstrXpath->registerNamespace('WST', static::WST);
            $rstrXpath->registerNamespace('WSP', static::WSP);
            $rstrXpath->registerNamespace('SC', static::SC);
    
            // parse security context token
            $securityContextTokenReference = $rstrXpath->query("//S12:Envelope/S12:Body/WST:RequestSecurityTokenResponse/WST:RequestedSecurityToken/SC:SecurityContextToken");
            $this->tokenReference = $securityContextTokenReference->item(0)->getAttribute('u:Id');
    
            $securityContextToken = $rstrXpath->query("//S12:Envelope/S12:Body/WST:RequestSecurityTokenResponse/WST:RequestedSecurityToken/SC:SecurityContextToken/SC:Identifier");
            $this->token = $securityContextToken->item(0)->nodeValue;
    
            $securityContextToken = $rstrXpath->query("//S12:Envelope/S12:Body/WST:RequestSecurityTokenResponse/WST:Entropy/WST:BinarySecret");
    
            $this->rstrBinarySecret = base64_decode( $securityContextToken->item(0)->nodeValue );
        }
    
        protected function execCurl( $request )
        {
            // Request
            $ch = curl_init();
    
            curl_setopt($ch, CURLOPT_URL, $this->stsEndpoint);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // disable SSL verification - re-enable if needed
            curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                "Host: " . $this->stsHostName,
                "Content-Type: application/soap+xml; charset=utf-8",
                "Content-Length: " . strlen( $request ),
            ));
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $request );
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
            $response = curl_exec($ch);
    
            if ( $response === false ) {
                throw new \Exception(curl_error($ch));
            }
    
            curl_close($ch);
    
            return $response;
        }
    
        /**
         * Generates UUID
         *
         * @return string UUID
         */
        protected function uuid()
        {
            return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', //
                mt_rand(0, 0xffff), //
                mt_rand(0, 0xffff), //
                mt_rand(0, 0xffff), //
                mt_rand(0, 0x0fff) | 0x4000, //
                mt_rand(0, 0x3fff) | 0x8000, //
                mt_rand(0, 0xffff), //
                mt_rand(0, 0xffff), //
                mt_rand(0, 0xffff) //
            );
        }
    
    
        /**
         * Calculate psha1 hash used for signature generation
         * @param unknown $clientSecret
         * @param unknown $serverSecret
         * @param number $sizeBits
         * @return string
         */
        protected function psha1($clientSecret, $serverSecret, $sizeBits = 256)
        {
            $sizeBytes = $sizeBits / 8;
    
            $hmacKey = $clientSecret;
            $hashSize = 160; // HMAC_SHA1 length is always 160
            $bufferSize = $hashSize / 8 + strlen($serverSecret);
            $i = 0;
    
            $b1 = $serverSecret;
            $b2 = "";
            $temp = null;
            $psha = array();
    
            while ($i < $sizeBytes) {
                $b1 = hash_hmac('SHA1', $b1, $hmacKey, true);
                $b2 = $b1 . $serverSecret;
                $temp = hash_hmac('SHA1', $b2, $hmacKey, true);
    
                for ($j = 0; $j < strlen($temp); $j++) {
                    if ($i < $sizeBytes) {
                        $psha[$i] = $temp[$j];
                        $i++;
                    } else {
                        break;
                    }
                }
            }
    
            return implode("", $psha);
        }
    }
    

    所以在请求中得到这样的东西:

    <s:Header>
    <a:Action s:mustUnderstand="1">https://some.url/NamespaceName/IServices/CheckTransaction</a:Action>
    ...
    </s:Header>
    <s:Body>
        <CheckTransaction xmlns="https://sime.url/ActionToDo">
            <TransactionID>1234567</TransactionID>
        </CheckTransaction>
    </s:Body>
    

    代码将是:

    $url = 'https://some.url/Services.svc';
    $namespace = 'https://some.url/NamespaceName'; // this is action namespace you need, since there is no WSDL parsing you need to set it by yourself
    
    try {
        $c = new WSSoap( $username, $password, $url, $namespace );
        $params = array(
            'TransactionID' => '1234567'
        );
        $r = $c->request( 'CheckTransaction', 'https://some.url/NamespaceName/IServices/CheckTransaction', $params ); // also applies - no WSDL parsing so we need to set params
    } catch (Exception $e) {
        throw $e;
    }