Connecting to WS-Security protected Web Service with PHP
The problem seems to be that the WSDL document is somehow protected (basic authentication - I don't thinkg that digest authentication is supported with SoapClient
, so you'd be out of luck in this case) and that the SoapClient
therefore cannot read and parse the service description.
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 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:
Add the login credentials to the WSDL url on the
SoapClient
constructor call$client = new SoapClient(
'https://' . urlencode($login) . ':' . urlencode($password) . '@example.com/WSDL/nameofservice',
array(
'login' => $login,
'password' => $password
)
);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
ext/curl
or manually through your browser or viawget
for example, store it on disk and instantiate theSoapClient
with a reference to the local WSDL.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 SoapClient
chokes already on reading the service descripion document before issuing a call to the service itself.
EDIT:
Having the WSDL file locally is a first step - this will allow the 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.
The second problem now is that 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.
A simple SoapClient
extension would be
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);
}
}
For a basic WS-Security authentication you would have to add the following to the SOAP-header:
<wsse:UsernameToken>
<wsse:Username>userid</wsse:Username>
<wsse:Password>password</wsse:Password>
</wsse:UsernameToken>
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)
Create WSSE Auth SOAP call for WebServices API in PHP using SoapClient
SOLVED: I finally got this going with the following code:
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);
}
}
$username = 'test';
$password = 'test123';
$wsdl = 'https://yoururl.com/api.asmx?WSDL';
$wsse_header = new WsseAuthHeader($username, $password);
$client = new SoapClient($wsdl, array(
//"trace" => 1,
//"exceptions" => 0
)
);
$client->__setSoapHeaders(array($wsse_header));
$request = array(
"functionResponseName" => array(
'param1' => "string",
"param2" => "string"
)
);
$results = $client->FunctionName($request);
var_dump($results);
PHP / DHL Soap Web service
I think you are mixing up some things. In the beginning you mention Basic Authentication but then your XML example shows a WS-Security header. Which one does the web service use?
The login
and password
options work only for Basic Authentication, as the SoapClient documentation says. But if your service requires Ws-Security as a SOAP header, then this won't work.
For using Ws-Security you need to extend your SoapClient to send it, as SoapClient doesn't support WS-Security out of the box. See for example:
- Connecting to WS-Security protected Web Service with PHP
- this example WS-Security for PHP SoapClient
- or using SoapHeader with SoapVar to create whatever header you like.
Connecting to a password protected Web Service with PHP
After a long struggle I found that as michaJIS said it is a session ID and one lust get this when requesting with the username and password. This Session ID must then be used in all other requests.
Related Topics
How to Insert Multiple Rows from a Single Query Using Eloquent/Fluent
How to Resize Pngs With Transparency in PHP
PHP $_Server['Http_Host'] Vs. $_Server['Server_Name'], Am I Understanding the Man Pages Correctly
What Are Register_Globals in PHP
PHP: Merge 2 Multidimensional Arrays
How to Create and Download a CSV File from PHP Script
Pdo With Insert into Through Prepared Statements
Reading and Writing Configuration Files
How to Find Out Where a Function Is Defined
Who Should Handle the Conditions in Complex Queries, the Data Mapper or the Service Layer
What Does "1" Mean At the End of a PHP Print_R Statement
Insert String At Specified Position
Warning: MySQL_Fetch_Array() Expects Parameter 1 to Be Resource, Boolean Given In
Convert Number to Month Name in PHP
Sqlstate[Hy000] [2002] Connection Refused Within Laravel Homestead