使用PHP连接到受WS-Security保护的Web服务 [英] Connecting to WS-Security protected Web Service with PHP
问题描述
我正在尝试连接到受密码保护且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,例如:
$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"添加到数组,但这不好.问题在于,我什至不知道我是否要到达远程站点,更不用说它是否拒绝了请求.
问题似乎是WSDL文档受到了某种保护(基本身份验证-我不认为SoapClient
支持摘要身份验证,所以您(在这种情况下会很不幸),因此SoapClient
无法读取和解析服务描述.
首先,您应该尝试在浏览器中打开WSDL位置,以检查是否显示了身份验证对话框.如果存在认证对话框,则必须确保SoapClient
在检索WSDL文档时使用必需的登录凭证.问题在于,在调用服务时,SoapClient
仅在创建客户端时发送使用login
和password
选项(以及使用证书身份验证时的local_cert
选项)提供的凭据,而不是在获取客户端时发送该凭据. WSDL(请参见此处).有两种方法可以解决此问题:
-
在
SoapClient
构造函数调用上将登录凭据添加到WSDL URL$client = new SoapClient( 'https://' . urlencode($login) . ':' . urlencode($password) . '@example.com/WSDL/nameofservice', array( 'login' => $login, 'password' => $password ) );
这应该是最简单的解决方案-但在PHP中错误#27777 据记载,这也不起作用(我没有尝试过).
-
使用HTTP流包装器或
ext/curl
手动获取WSDL,或者例如通过浏览器或通过wget
手动获取WSDL,将其存储在磁盘上,并引用本地WSDL实例化SoapClient
.如果WSDL文档发生更改(因为您必须检测到更改并将新版本存储在磁盘上),则此解决方案可能会出现问题.
如果未显示身份验证对话框,并且您可以在浏览器中阅读WSDL,则应提供更多详细信息,以检查其他可能的错误/问题.
这个问题最终与服务本身无关,因为SoapClient
在发出对服务本身的调用之前读取服务描述文档时已经窒息了.
在本地拥有WSDL文件是第一步-这将使SoapClient
知道如何与服务通信.可以直接从服务位置,从另一台服务器提供服务或从本地文件中读取WSDL,因为服务URL是在WSDL中编码的,因此SoapClient
始终知道在哪里寻找服务端点.>
第二个问题是SoapClient
不支持 WS-Security 规范本身,这意味着您必须扩展SoapClient
才能处理特定的标头.添加所需行为的扩展点是 SoapClient::__doRequest()
-在将XML有效负载发送到服务端点之前对其进行处理.但是我认为,自己实施WS-Security解决方案将需要对特定WS-Security规范有相当的了解.也许还可以使用 SoapHeader
s,但我对此表示怀疑这将起作用,将自定义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模块,则应该查看 and then go to the site on the browser, I get: If I try defining the service as a Soap Server, like: I get: I haven't tried sending a raw request envelope yet to see what the server returns, but that may be a workaround. But I was hoping someone could tell me how I can set it up using the php built-in classes. I tried adding "userName" and "password" to the array, but that was no good. The problem is that I can't even tell if I'm reaching the remote site at all, let alone whether it is refusing the request. The problem seems to be that the WSDL document is somehow protected (basic authentication - I don't thinkg that digest authentication is supported with First of all you should try to open the WSDL location in your browser to check if you're presented an authentication dialog. If there is an authentication dialog you must make sure that the Add the login credentials to the WSDL url on the This should be the most simple solution - but in PHP Bug #27777 it is written that this won't work either (I haven't tried that). Fetch the WSDL manually using the HTTP stream wrapper or This solution can be problematic if the WSDL document changes as you have to detect the change and store the new version on disk. If no authentication dialog is shown and if you can read the WSDL in your browser, you should provide some more details to check for other possible errors/problems. This problem is definitively not related to the service itself as EDIT: Having the WSDL file locally is a first step - this will allow the The second problem now is that A simple For a basic WS-Security authentication you would have to add the following to the SOAP-header: But as I said above: I think that much more knowledge about the WS-Security specification and the given service architecture is needed to get this working. If you need an enterprise grade solution for the whole WS-* specification range and if you can install PHP modules you should have a look at the WSO2 Web Services Framework for PHP (WSO2 WSF/PHP) 这篇关于使用PHP连接到受WS-Security保护的Web服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!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
$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>
SoapClient
, so you'd be out of luck in this case) and that the SoapClient
therefore cannot read and parse the service description. SoapClient
uses the required login credentials on retrieving the WSDL document. The problem is that SoapClient
will only send the credentials given with the login
and password
options (as well as the local_cert
option when using certificate authentication) on creating the client when invoking the service, not when fetching the WSDL (see here). There are two methods to overcome this problem:
SoapClient
constructor call$client = new SoapClient(
'https://' . urlencode($login) . ':' . urlencode($password) . '@example.com/WSDL/nameofservice',
array(
'login' => $login,
'password' => $password
)
);
ext/curl
or manually through your browser or via wget
for example, store it on disk and instantiate the SoapClient
with a reference to the local WSDL. SoapClient
chokes already on reading the service descripion document before issuing a call to the service itself.SoapClient
to know how to communicate with the service. It doesn't matter if the WSDL is directly served from the service location, from another server or is read from a local file - service urls are coded within the WSDL so SoapClient
always knows where to look for the service endpoint.SoapClient
has no support for the WS-Security specifications natively, which means you must extend SoapClient
to handle the specific headers. An extension point to add the required behaviour would be SoapClient::__doRequest()
which pre-processes the XML payload before sending it to the service endpoint. But I think that implementing the WS-Security solution yourself will require a decent knowledge of the specific WS-Security specifications. Perhaps WS-Security headers can also be created and packed into the XML request by using SoapClient::__setSoapHeaders()
and the appropriate SoapHeader
s but I doubt that this will work, leaving the custom SoapClient
extension as the lone possibility.SoapClient
extension would beclass 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);
}
}
<wsse:UsernameToken>
<wsse:Username>userid</wsse:Username>
<wsse:Password>password</wsse:Password>
</wsse:UsernameToken>