How to consume a WCF Web Service that uses custom username validation with a PHP page?
I solved the problem. I had to extends the "SoapHeader" class in PHP to make it compliant with the WS-Security standard.
Here is the solution :
PHP Header class
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);
}
}
PHP Client call
$options = array(
'soap_version' => SOAP_1_1,
'exceptions' => true,
'trace' => 1,
'wdsl_local_copy' => true
);
$username = "MyUser";
$password = "MyPassword";
$wsse_header = new WsseAuthHeader($username, $password);
$client = new SoapClient('https://UrlToService/Service.svc?wsdl', $options);
$client->__setSoapHeaders(array($wsse_header));
try
{
$phpresponse = $client->Get();
print $phpresponse->GetResult->Version;
echo "</b><BR/><BR/>";
}
catch(Exception $e)
{
echo "<h2>Exception Error!</h2></b>";
echo $e->getMessage();
}
Hope it will helps someone else!
Thanks to Chris : Connecting to WS-Security protected Web Service with PHP
UserNamePasswordValidator with basicHttpbinding and ssl
ok I dont know how it works but apperently I did right all along. what made it to work was that in my php code when i specify username and password that the parameters was named.
$soapClient = new SoapClient("https://wsdl"), array('login' => "user123",
'password' => "pass123"));
and the same in my custom username password validator. i had userName
and Password
before on both places and that didnt work. So i dont know the difference but it works now... maybe someone else can answer why it works this way.
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)
Related Topics
Chart.Js - Getting Data from Database Using MySQL and PHP
PHP Array VS [ ] in Method and Variable Declaration
What Is the Postman-Token Header Attribute in Generated Code from Postman
Alternative to Money_Format() Function
What Does the Dot-Slash Do to PHP Include Calls
PHP Curl How to Add the User Agent Value or Overcome the Servers Blocking Curl Requests
PHP Convert Decimal into Fraction and Back
How to Execute My SQL Query in Codeigniter
Get the Price of an Item on Steam Community Market with PHP and Regex
Xml Creation Using Codeigniter
Designing a Secure Auto Login Cookie System in PHP
Codeigniter - File Upload Required Validation
Select Within 20 Kilometers Based on Latitude/Longitude
How to Create a New Joomla User Account from Within a Script