带有 WCF BadContextToken 的 PHP Soap 客户端

2024-05-08

经过几天的谷歌-in/尝试/脱发,我仍然找不到解决方案,所以请帮助:)

简短信息:
我需要使用 PHP(SOAP 客户端)的 WCF 服务。它使用 wsHttpBinding (ws-security) 并且无法设置 basicHttpBinding。一切都在 VPN 后面,所以我无法为您提供网络服务的链接。此外,数据被认为是秘密的(来自客户的请求),所以我不能给你完整的信息,只能给你一些“常见”的东西。这是WS配置:

<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 https://code.google.com/p/wse-php但没有成功,请求仍然挂起。
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 相同),尝试使用随机数、散列密码、纯密码和一堆我现在不记得的其他内容。
我也尝试编译wso2/wsf https://github.com/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 来使用吗(如果可以,请帮助理解如何使用)?


我前段时间解决了这个问题,但从来没有时间让它变得更“更好”。所以一般问题是 wsHttpBinding 消息安全性的工作方式以及如何在 PHP 上实现它。我使用的概念来自https://github.com/enginaygen/kps-soap-client/blob/master/KPSSoapClient.php https://github.com/enginaygen/kps-soap-client/blob/master/KPSSoapClient.php还添加了 psha1P_SHA1算法在PHP中的实现 https://stackoverflow.com/questions/19590675/implementation-of-p-sha1-algorithm-in-php.

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

  1. PHP 从 WSS 请求安全令牌及其请求密钥
  2. WS 生成安全令牌并将其返回给 PHP
  3. PHP 生成带有请求的安全令牌的 SOAP C14N 签名消息

这是实现(注意:由于 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;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

带有 WCF BadContextToken 的 PHP Soap 客户端 的相关文章