2009-06-04 89 views
28

我想连接到受密码保护的Web服务,并且URL是https。我无法弄清楚如何在脚本发出请求之前进行身份验证。它似乎是在我定义服务后立即发出请求。举例来说,如果我把:用PHP连接到受WS-Security保护的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> 

我没有尝试发送原始请求信封以查看服务器返回的内容,但这可能是一种解决方法。但我希望有人能告诉我如何使用php内置类来设置它。我试图向数组中添加“userName”和“password”,但这并不好。问题是,我甚至无法告诉我是否到达远程站点,更不用说它是否拒绝了请求。

回答

27

这个问题似乎是WSDL文档以某种方式保护(基本身份验证 - 我不认为thinkg摘要式身份验证支持与SoapClient,所以你会倒霉的这种情况),因此SoapClient无法读取和解析服务描述。

首先,您应该尝试在浏览器中打开WSDL位置,以检查是否提供了验证对话框。如果存在认证对话框,则必须确保SoapClient在检索WSDL文档时使用所需的登录凭证。问题是SoapClient只会在调用服务时发送凭证loginpassword选项(以及使用证书身份验证时的local_cert选项)给出的凭证,而不是在获取WSDL时发送凭证(请参阅here)。有两种方法来解决这个问题:

  1. 添加的登录凭据WSDL URL上SoapClient构造函数调用

    $client = new SoapClient(
        'https://' . urlencode($login) . ':' . urlencode($password) . '@example.com/WSDL/nameofservice', 
        array(
         'login' => $login, 
         'password' => $password 
        ) 
    ); 
    

    这应该是最简单的解决方案 - 但在PHP Bug #27777它是写道这也行不通(我没有尝试过)。

  2. 抓取WSDL使用HTTP流包装或ext/curl或手动通过浏览器或经由wget例如手动,它存储在磁盘上,并与到本地WSDL参考实例化SoapClient

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

如果没有显示身份验证对话框,如果你能在浏览器中读取WSDL,你应该提供一些更多的细节,以检查其他可能的错误/问题。

此问题与服务本身无关,因为SoapClient窒息已经在发出服务本身调用之前阅读服务描述文档。

编辑:

有本地的WSDL文件是第一步 - 这将允许SoapClient知道如何与服务通信。无论WSDL是直接从服务位置,从另一个服务器提供还是从本地文件读取服务 - 服务URL都在WSDL内编码,因此SoapClient总是知道在哪里查找服务端点。

现在的第二个问题是SoapClient原生不支持WS-Security规范,这意味着您必须扩展SoapClient来处理特定的头文件。添加所需行为的扩展点是SoapClient::__doRequest(),它将XML负载发送到服务端点之前进行预处理。但我认为自己实施WS-Security解决方案需要对WS-Security特定规范有相当了解。也许WS-Security头文件也可以通过使用SoapClient::__setSoapHeaders()和适当的SoapHeader创建并打包到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); 
    } 
} 

对于基本的WS-Security认证,就必须添加以下到SOAP头:

<wsse:UsernameToken> 
    <wsse:Username>userid</wsse:Username> 
    <wsse:Password>password</wsse:Password>         
</wsse:UsernameToken> 

但正如我上面说的:我认为需要更多关于WS-Security规范和给定服务体系结构的知识才能实现这一目标。

如果你需要为整个WS- *规范范围内的企业级解决方案,如果你可以安装PHP模块,你应该有一个看看WSO2 Web Services Framework for PHP (WSO2 WSF/PHP)

+0

哇。感谢一个伟大的开局。好的,更具体的说,这不是一个http认证。如果我去了网址,我会重定向到登录,然后我可以看到服务。在这个服务的文档中,它表示应该在SOAP头中设置用户认证,但很明显我看到了如何在PHP中做到这一点。 (这被称为WS-security,继续我的最后一次谷歌搜索,它似乎没有很好地在PHP中实现。)如果您知道设置标头的方法,或使用其他方法(如curl)来获取服务和然后将其传递给SoapClient,这将是有启发性的。 – Anthony 2009-06-05 16:34:35

+0

哦!快速的问题...所以我将服务复制到我的服务器上的一个文件,我可以设置它。我仍然不清楚如何真正解决这个问题。但是你是否说服务不在其实际位置时可以使用? – Anthony 2009-06-05 16:36:36

+1

使用SoapClient对WS-Security和SOAP请求操作的某些观点进行了扩展。 – 2009-06-05 17:36:43

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

From the php documentation

+0

这是我做的第一件事,同样的错误,我记得。身份验证进入头文件(根据此特定服务的文档),并且我不认为在定义客户端之前我才能更改头文件,但是到那时为止抛出错误。 – Anthony 2009-06-04 23:56:43

6

我比扩展现有SoapClient的更简单的解决方案图书馆。

第一步:创建两个类为WSSE头创建结构

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; 
    } 
} 

第二步:用户名和密码创建SOAP变量

$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); 

步骤3:为验证类创建对象,并通过在肥皂var

$objWSSEAuth = new clsWSSEAuth($objSoapVarUser, $objSoapVarPass); 

第4步:创建SoapVar作为Auth类的对象

$objSoapVarWSSEAuth = new SoapVar($objWSSEAuth, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'UsernameToken', $strWSSENS); 

第五步:令牌类中创建对象

$objWSSEToken = new clsWSSEToken($objSoapVarWSSEAuth); 

第六步:创建SoapVar了Token类的对象

$objSoapVarWSSEToken = new SoapVar($objWSSEToken, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'UsernameToken', $strWSSENS); 

第七步:对 '安全' 节点创建SoapVar

$objSoapVarHeaderVal=new SoapVar($objSoapVarWSSEToken, SOAP_ENC_OBJECT, NULL, $strWSSENS, 'Security', $strWSSENS); 

第8步:创建标题反对出于对安全的soapvar

$objSoapVarWSSEHeader = new SoapHeader($strWSSENS, 'Security', $objSoapVarHeaderVal,true, 'http://abce.com'); 

//Third parameter here makes 'mustUnderstand=1 
//Forth parameter generates 'actor="http://abce.com"' 

第九步:创建SOAP客户端的对象

$objClient = new SoapClient($WSDL, $arrOptions); 

第十步:设置标题为SoapClient的对象

$objClient->__setSoapHeaders(array($objSoapVarWSSEHeader)); 

第11步:最后调用方法

$objResponse = $objClient->__soapCall($strMethod, $requestPayloadString); 
28

只要往往在SOAPHEADER创建WSSE compilant认证:

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)); 

如果需要使用WS-Security与现时和时间戳,彼得已经发布了更新版本的http://php.net/manual/en/soapclient.soapclient.php#114976其中他写道,它确实为他工作:

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); 
    } 
} 

比较,以及与答案https://stackoverflow.com/a/18575154/367456

15

给出口令的细节消化安全性,您可以使用弗洛翼:

/** 
    * 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); 
    } 

用PHP SoapClient的使用它,用这样的方式:

$client = new SoapClient('http://endpoint'); 
$client->__setSoapHeaders(soapClientWSSecurityHeader('myUser', 'myPassword')); 
// $client->myService(array('param' => 'value', ...); 
0

我采用了阿兰Tiemblo的优秀的解决方案,但是我用的密码,而不是摘要。

/** 
    * 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)); 
0

WS安全与消化密码。这对我来说工作代码:

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)]);