使用PHP连接到受WS-Security保护的Web服务

使用PHP连接到受WS-Security保护的Web服务,php,web-services,soap,ws-security,Php,Web Services,Soap,Ws Security,我正在尝试连接到一个受密码保护且url为https的Web服务。在脚本发出请求之前,我不知道如何进行身份验证。我一定义服务,它就会发出请求。例如,如果我输入: $client = new SoapClient("https://example.com/WSDL/nameofservice", array('trace' => 1,) ); 然后在浏览器上访问站点,我得到: Fatal error: Uncaught SoapFault exception: [WSDL]

我正在尝试连接到一个受密码保护且url为https的Web服务。在脚本发出请求之前,我不知道如何进行身份验证。我一定义服务,它就会发出请求。例如,如果我输入:

$client = new SoapClient("https://example.com/WSDL/nameofservice",
       array('trace' => 1,)
);
然后在浏览器上访问站点,我得到:

Fatal error: Uncaught SoapFault exception: 
[WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from
'https://example.com/WSDL/nameofservice' in /path/to/my/script/myscript.php:2 
Stack trace: #0 /path/to/my/script/myscript.php(2): 
SoapClient->SoapClient('https://example...', Array) #1 {main} thrown in 
/path/to/my/script/myscript.php on line 2
如果我尝试将服务定义为Soap服务器,如:

$server= new SoapServer("https://example.com/WSDL/nameofservice");
我得到:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>WSDL</faultcode>
<faultstring>
SOAP-ERROR: Parsing WSDL: 
Couldn't load from 'https://example.com/WSDL/nameofservice'
</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

WSDL
SOAP-ERROR:解析WSDL:
无法从“”加载https://example.com/WSDL/nameofservice'
我还没有尝试发送原始请求信封来查看服务器返回的内容,但这可能是一个解决方法。但我希望有人能告诉我如何使用php内置类进行设置。我尝试将“用户名”和“密码”添加到数组中,但效果不佳。问题是,我甚至不知道我是否到达了远程站点,更不用说它是否拒绝了请求

$client = new SoapClient("some.wsdl", array('login'    => "some_name",
                                            'password' => "some_password"));

问题似乎在于WSDL文档受到某种程度的保护(基本身份验证-我认为
SoapClient
不支持摘要身份验证,因此在这种情况下您会运气不佳),因此
SoapClient
无法读取和解析服务描述

首先,您应该尝试在浏览器中打开WSDL位置,以检查是否显示了身份验证对话框。如果存在身份验证对话框,则必须确保
SoapClient
在检索WSDL文档时使用所需的登录凭据。问题是,
SoapClient
在调用服务时,而不是在获取WSDL时,只在创建客户端时发送使用
登录
密码
选项(以及使用证书身份验证时的
本地证书
选项)提供的凭据(请参阅)。有两种方法可以克服此问题:

  • SoapClient
    constructor调用中将登录凭据添加到WSDL url

    $client = new SoapClient(
        'https://' . urlencode($login) . ':' . urlencode($password) . '@example.com/WSDL/nameofservice',
        array(
            'login' => $login,
            'password' => $password
        )
    );
    
    这应该是最简单的解决方案——但在PHP中,有人认为这也行不通(我还没有尝试过)

  • 使用HTTP流包装器或
    ext/curl
    手动获取WSDL,或通过浏览器或
    wget
    手动获取WSDL。例如,将其存储在磁盘上,并引用本地WSDL实例化
    SoapClient

    如果WSDL文档发生更改,则此解决方案可能会出现问题,因为您必须检测更改并将新版本存储在磁盘上

  • 如果未显示身份验证对话框,并且如果您可以在浏览器中读取WSDL,则应提供更多详细信息以检查其他可能的错误/问题

    这个问题肯定与服务本身无关,因为在发出对服务本身的调用之前,
    SoapClient
    已经在读取服务描述文档时阻塞了

    编辑:

    在本地拥有WSDL文件是第一步-这将允许
    SoapClient
    知道如何与服务通信。不管WSDL是直接从服务位置、从另一台服务器还是从本地文件读取,服务URL都是在WSDL中编码的,因此
    SoapClient
    始终知道在哪里查找服务端点

    第二个问题是,
    SoapClient
    本机不支持规范,这意味着您必须扩展
    SoapClient
    来处理特定的头。添加所需行为的扩展点是在将XML有效负载发送到服务端点之前对其进行预处理。但是我认为自己实现WS-Security解决方案需要对特定的WS-Security规范有相当的了解。也许WS-Security头也可以通过使用和适当的s来创建并打包到XML请求中,但我怀疑这是否会起作用,只剩下定制的
    SoapClient
    扩展

    一个简单的
    SoapClient
    扩展是

    class My_SoapClient extends SoapClient
    {
        protected function __doRequest($request, $location, $action, $version) 
        {
            /*
             * $request is a XML string representation of the SOAP request
             * that can e.g. be loaded into a DomDocument to make it modifiable.
             */
            $domRequest = new DOMDocument();
            $domRequest->loadXML($request);
    
            // modify XML using the DOM API, e.g. get the <s:Header>-tag 
            // and add your custom headers
            $xp = new DOMXPath($domRequest);
            $xp->registerNamespace('s', 'http://www.w3.org/2003/05/soap-envelope');
            // fails if no <s:Header> is found - error checking needed
            $header = $xp->query('/s:Envelope/s:Header')->item(0);
    
            // now add your custom header
            $usernameToken = $domRequest->createElementNS('http://schemas.xmlsoap.org/ws/2002/07/secext', 'wsse:UsernameToken');
            $username = $domRequest->createElementNS('http://schemas.xmlsoap.org/ws/2002/07/secext', 'wsse:Username', 'userid');
            $password = $domRequest->createElementNS('http://schemas.xmlsoap.org/ws/2002/07/secext', 'wsse:Password', 'password');
            $usernameToken->appendChild($username);
            $usernameToken->appendChild($password);
            $header->appendChild($usernameToken);
    
            $request = $domRequest->saveXML();
            return parent::__doRequest($request, $location, $action, $version);
        }
    }
    
    class My_SoapClient扩展了SoapClient
    {
    受保护的函数\uuu doRequest($request、$location、$action、$version)
    {
    /*
    *$request是SOAP请求的XML字符串表示形式
    *例如,可以加载到DomDocument中以使其可修改。
    */
    $domRequest=新的DOMDocument();
    $domRequest->loadXML($request);
    //使用DOM API修改XML,例如获取-标记
    //并添加自定义标题
    $xp=新的DOMXPath($domRequest);
    $xp->registerNamespace('s','http://www.w3.org/2003/05/soap-envelope');
    //如果未找到,则失败-需要进行错误检查
    $header=$xp->query('/s:信封/s:header')->项(0);
    //现在添加自定义标题
    $usernameToken=$domRequest->CreateElements('http://schemas.xmlsoap.org/ws/2002/07/secext'、'wsse:UsernameToken');
    $username=$domRequest->createElements('http://schemas.xmlsoap.org/ws/2002/07/secext'、'wsse:Username'、'userid');
    $password=$domRequest->createElements('http://schemas.xmlsoap.org/ws/2002/07/secext“,”wsse:Password“,”Password“);
    $usernameToken->appendChild($username);
    $usernameToken->appendChild($password);
    $header->appendChild($usernameToken);
    $request=$domRequest->saveXML();
    返回父项::_doRequest($request、$location、$action、$version);
    }
    }
    
    对于基本WS-Security身份验证,您必须将以下内容添加到SOAP头中:

    <wsse:UsernameToken>
        <wsse:Username>userid</wsse:Username>
        <wsse:Password>password</wsse:Password>                                 
    </wsse:UsernameToken>
    
    
    用户ID
    密码
    
    但正如我上面所说的:我认为需要更多关于WS-Security规范和给定服务体系结构的知识才能使其正常工作

    如果您需要针对整个WS-*规范范围的企业级解决方案,并且如果您可以安装PHP模块,您应该
    class clsWSSEAuth {
        private $Username;
        private $Password;
        function __construct($username, $password) {
            $this->Username=$username;
            $this->Password=$password;
        }
    }
    
    class clsWSSEToken {
        private $UsernameToken;
        function __construct ($innerVal){
            $this->UsernameToken = $innerVal;
        }
    }
    
    $username = 1111;
    $password = 1111;
    
    //Check with your provider which security name-space they are using.
    $strWSSENS = "http://schemas.xmlsoap.org/ws/2002/07/secext";
    
    $objSoapVarUser = new SoapVar($username, XSD_STRING, NULL, $strWSSENS, NULL, $strWSSENS);
    $objSoapVarPass = new SoapVar($password, XSD_STRING, NULL, $strWSSENS, NULL, $strWSSENS);
    
    $objWSSEAuth = new clsWSSEAuth($objSoapVarUser, $objSoapVarPass);
    
    $objSoapVarWSSEAuth = new SoapVar($objWSSEAuth, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'UsernameToken', $strWSSENS);
    
    $objWSSEToken = new clsWSSEToken($objSoapVarWSSEAuth);
    
    $objSoapVarWSSEToken = new SoapVar($objWSSEToken, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'UsernameToken', $strWSSENS);
    
    $objSoapVarHeaderVal=new SoapVar($objSoapVarWSSEToken, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'Security', $strWSSENS);
    
    $objSoapVarWSSEHeader = new SoapHeader($strWSSENS, 'Security', $objSoapVarHeaderVal,true, 'http://abce.com');
    
    //Third parameter here makes 'mustUnderstand=1
    //Forth parameter generates 'actor="http://abce.com"'
    
    $objClient = new SoapClient($WSDL, $arrOptions);
    
    $objClient->__setSoapHeaders(array($objSoapVarWSSEHeader));
    
    $objResponse = $objClient->__soapCall($strMethod, $requestPayloadString);
    
    class WsseAuthHeader extends SoapHeader {
    
    private $wss_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
    
    function __construct($user, $pass, $ns = null) {
        if ($ns) {
            $this->wss_ns = $ns;
        }
    
        $auth = new stdClass();
        $auth->Username = new SoapVar($user, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns); 
        $auth->Password = new SoapVar($pass, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
    
        $username_token = new stdClass();
        $username_token->UsernameToken = new SoapVar($auth, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns); 
    
        $security_sv = new SoapVar(
            new SoapVar($username_token, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns),
            SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'Security', $this->wss_ns);
        parent::__construct($this->wss_ns, 'Security', $security_sv, true);
    }
    }
    
    
    
    $wsse_header = new WsseAuthHeader($username, $password);
    $x = new SoapClient('{...}', array("trace" => 1, "exception" => 0));
    $x->__setSoapHeaders(array($wsse_header));
    
    class WsseAuthHeader extends SoapHeader
    {
        private $wss_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
        private $wsu_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
    
        function __construct($user, $pass)
        {
            $created    = gmdate('Y-m-d\TH:i:s\Z');
            $nonce      = mt_rand();
            $passdigest = base64_encode(pack('H*', sha1(pack('H*', $nonce) . pack('a*', $created) . pack('a*', $pass))));
    
            $auth           = new stdClass();
            $auth->Username = new SoapVar($user, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
            $auth->Password = new SoapVar($pass, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
            $auth->Nonce    = new SoapVar($passdigest, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
            $auth->Created  = new SoapVar($created, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wsu_ns);
    
            $username_token                = new stdClass();
            $username_token->UsernameToken = new SoapVar($auth, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns);
    
            $security_sv = new SoapVar(
                new SoapVar($username_token, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns),
                SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'Security', $this->wss_ns);
            parent::__construct($this->wss_ns, 'Security', $security_sv, true);
        }
    }
    
       /**
        * This function implements a WS-Security digest authentification for PHP.
        *
        * @access private
        * @param string $user
        * @param string $password
        * @return SoapHeader
        */
       function soapClientWSSecurityHeader($user, $password)
       {
          // Creating date using yyyy-mm-ddThh:mm:ssZ format
          $tm_created = gmdate('Y-m-d\TH:i:s\Z');
          $tm_expires = gmdate('Y-m-d\TH:i:s\Z', gmdate('U') + 180); //only necessary if using the timestamp element
    
          // Generating and encoding a random number
          $simple_nonce = mt_rand();
          $encoded_nonce = base64_encode($simple_nonce);
    
          // Compiling WSS string
          $passdigest = base64_encode(sha1($simple_nonce . $tm_created . $password, true));
    
          // Initializing namespaces
          $ns_wsse = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
          $ns_wsu = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
          $password_type = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest';
          $encoding_type = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary';
    
          // Creating WSS identification header using SimpleXML
          $root = new SimpleXMLElement('<root/>');
    
          $security = $root->addChild('wsse:Security', null, $ns_wsse);
    
          //the timestamp element is not required by all servers
          $timestamp = $security->addChild('wsu:Timestamp', null, $ns_wsu);
          $timestamp->addAttribute('wsu:Id', 'Timestamp-28');
          $timestamp->addChild('wsu:Created', $tm_created, $ns_wsu);
          $timestamp->addChild('wsu:Expires', $tm_expires, $ns_wsu);
    
          $usernameToken = $security->addChild('wsse:UsernameToken', null, $ns_wsse);
          $usernameToken->addChild('wsse:Username', $user, $ns_wsse);
          $usernameToken->addChild('wsse:Password', $passdigest, $ns_wsse)->addAttribute('Type', $password_type);
          $usernameToken->addChild('wsse:Nonce', $encoded_nonce, $ns_wsse)->addAttribute('EncodingType', $encoding_type);
          $usernameToken->addChild('wsu:Created', $tm_created, $ns_wsu);
    
          // Recovering XML value from that object
          $root->registerXPathNamespace('wsse', $ns_wsse);
          $full = $root->xpath('/root/wsse:Security');
          $auth = $full[0]->asXML();
    
          return new SoapHeader($ns_wsse, 'Security', new SoapVar($auth, XSD_ANYXML), true);
       }
    
    $client = new SoapClient('http://endpoint');
    $client->__setSoapHeaders(soapClientWSSecurityHeader('myUser', 'myPassword'));
    // $client->myService(array('param' => 'value', ...);
    
        /**
        * This function implements a WS-Security authentication for PHP.
        *
        * @access private
        * @param string $user
        * @param string $password
        * @return SoapHeader
        */
        function soapClientWSSecurityHeader($user, $password)
       {
          // Creating date using yyyy-mm-ddThh:mm:ssZ format
          $tm_created = gmdate('Y-m-d\TH:i:s\Z');
          $tm_expires = gmdate('Y-m-d\TH:i:s\Z', gmdate('U') + 180); //only necessary if using the timestamp element
    
          // Generating and encoding a random number
          $simple_nonce = mt_rand();
          $encoded_nonce = base64_encode($simple_nonce);
    
          // Compiling WSS string
          $passdigest = base64_encode(sha1($simple_nonce . $tm_created . $password, true));
    
          // Initializing namespaces
          $ns_wsse = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
          $ns_wsu = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
          $password_type = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText';
          $encoding_type = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary';
    
          // Creating WSS identification header using SimpleXML
          $root = new SimpleXMLElement('<root/>');
    
          $security = $root->addChild('wsse:Security', null, $ns_wsse);
    
          //the timestamp element is not required by all servers
          $timestamp = $security->addChild('wsu:Timestamp', null, $ns_wsu);
          $timestamp->addAttribute('wsu:Id', 'Timestamp-28');
          $timestamp->addChild('wsu:Created', $tm_created, $ns_wsu);
          $timestamp->addChild('wsu:Expires', $tm_expires, $ns_wsu);
    
          $usernameToken = $security->addChild('wsse:UsernameToken', null, $ns_wsse);
          $usernameToken->addChild('wsse:Username', $user, $ns_wsse);
          $usernameToken->addChild('wsse:Password', $password, $ns_wsse)->addAttribute('Type', $password_type);
          $usernameToken->addChild('wsse:Nonce', $encoded_nonce, $ns_wsse)->addAttribute('EncodingType', $encoding_type);
          $usernameToken->addChild('wsu:Created', $tm_created, $ns_wsu);
    
          // Recovering XML value from that object
          $root->registerXPathNamespace('wsse', $ns_wsse);
          $full = $root->xpath('/root/wsse:Security');
          $auth = $full[0]->asXML();
    
          return new SoapHeader($ns_wsse, 'Security', new SoapVar($auth, XSD_ANYXML), true);
       }
    
    $client = new SoapClient('YOUR ENDPOINT');
    $userid = "userid";
    $password = "password"; 
    $client->__setSoapHeaders(soapClientWSSecurityHeader($userid,$password));
    
    class WsseAuthHeader extends SoapHeader {
    
        private $wss_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
        private $wsu_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd';
        private $type_password_digest= 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest';
        private $type_password_text= 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText';
        private $encoding_type_base64 = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary';
    
        private function authText($user, $pass) {
            $auth = new stdClass();
            $auth->Username = new SoapVar($user, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
            $auth->Password = new SoapVar('<ns2:Password Type="'.$this->type_password_text.'">' . $pass . '</ns2:Password>', XSD_ANYXML );
            return $auth;
        }
    
        private function authDigest($user, $pass) {
            $created = gmdate('Y-m-d\TH:i:s\Z');
            $nonce = mt_rand();
            $enpass = base64_encode(pack('H*', sha1(pack('H*', $nonce) . pack('a*', $created) . pack('a*', $pass))));
            $auth = new stdClass();
            $auth->Username = new SoapVar($user, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
            $auth->Password = new SoapVar('<ns2:Password Type="'.$this->type_password_digest.'">' . $enpass . '</ns2:Password>', XSD_ANYXML );
            $auth->Nonce = new SoapVar('<ns2:Nonce EncodingType="' . $this->encoding_type_base64 . '">' . base64_encode(pack('H*', $nonce)) . '</ns2:Nonce>', XSD_ANYXML);
            $auth->Created = new SoapVar($created, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wsu_ns);
            return $auth;
        }
    
        function __construct($user, $pass, $useDigest=true) {
            if ($useDigest) {
                $auth = $this->authDigest($user, $pass);
            }else{
                $auth = $this->authText($user, $pass);
            }
            $username_token = new stdClass();
            $username_token->UsernameToken = new SoapVar($auth, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns);
    
            $security_sv = new SoapVar(
                new SoapVar($username_token, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns),
                SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'Security', $this->wss_ns);
            parent::__construct($this->wss_ns, 'Security', $security_sv, true);
        }
    }
    
     $client->__setSoapHeaders([new WsseAuthHeader($login, $password)]);