Asked  7 Months ago    Answers:  5   Viewed   46 times

I'm having hard time consuming a secure WCF Web Service from a PHP site. My knowledge in PHP are limited, I found various examples on the Web but didn't succeeded making them working yet.

I have a Silverlight application that also consume this WebService and it works fine. But when I run the PHP site, I get this error :

MessageSecurityException: Security processor was unable to find a security header in the message. This might be because the message is an unsecured fault or because there is a binding mismatch between the communicating parties. This can occur if the service is configured for security and the client is not using security.

I also tried to change the customBinding to a basicHttpBinding. When I do that, the Custom Username validator is not called anymore. But, using basicHttpBinding and removing credentials validation on my WCF Service "works" (Besides the fact that it is unsecured). So the problem seems to related to the security message.

Could someone give me a working example or tutorial that could help me making this PHP page works?

Here is my PHP code :

    $options = array( 
            'soap_version'  => SOAP_1_1, 
            'exceptions'    => true, 
            'trace'         => 1, 
            'cache_wsdl'    => WSDL_CACHE_NONE,
            'Username'      => 'MyUserName', 
            'password'      => 'MyPassword');

$client = new SoapClient('https://UrlToService/Service.svc?wsdl', $options); 

try
{
    $phpresponse = $client->Get(); 

    print $phpresponse->GetResult->Version;
    echo "</b><BR/><BR/>";
}
catch(Exception $e) 
{ 
    echo "<h2>Exception Error!</h2></b>"; 
    echo $e->getMessage(); 
    echo "<BR/><BR/>";
}

WCF Configuration

<behavior name="sslBehavior">
   <serviceMetadata httpsGetEnabled="true" />
   <serviceDebug includeExceptionDetailInFaults="true" />
   <serviceCredentials>
      <userNameAuthentication userNamePasswordValidationMode="Custom"   customUserNamePasswordValidatorType="MyNamespace.ServiceUserNameValidator, MyNamespace" />
   </serviceCredentials>
   <serviceSecurityAudit
    auditLogLocation="Application"
    serviceAuthorizationAuditLevel="Failure"
    messageAuthenticationAuditLevel="Failure"
    suppressAuditFailure="true" />
</behavior>

<customBinding>
    <binding name="sslCustomBinding">
      <security authenticationMode="UserNameOverTransport" includeTimestamp="true" allowInsecureTransport="true">
        <localServiceSettings maxClockSkew="00:10:00" />
        <localClientSettings maxClockSkew="00:10:00" />
        <secureConversationBootstrap />
      </security>
      <textMessageEncoding messageVersion="Soap11" />
      <httpsTransport />
    </binding>
  </customBinding>

<service behaviorConfiguration="sslBehavior" name="MyNamespace.Services.Service">
    <endpoint address="" binding="customBinding" 
              bindingConfiguration="sslCustomBinding" contract="MyNamespace.ServiceContracts.IService" />
    <host>
      <baseAddresses>
        <add baseAddress="https://UrlToService/Services/" />
      </baseAddresses>
    </host>
  </service>

 Answers

79

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

Wednesday, March 31, 2021
 
Besnik
answered 7 Months ago
71

You can inject all tagged services in one swoop, without having to use a compiler pass.

Configuration

Since you are already doing the tagging, as shown in the question, it's in only a matter of declaring the injection:

_instanceof:
    AppModelItemHandlerInterface:
        tags: ['item_handler']
        lazy: true

services:
    AppControllerItemUpdateController:
        arguments: !tagged 'item_handler'

Implementation

You would need to change the constructor for your controller so it accepts an iterable:

public function __construct(iterable $itemHandlers)
{
    $this->handlers = $itemHandlers;
}

In your class a RewindableGenerator will be injected with your services inside. You can simply iterate over it to get each of those.

This has been available since 3.4; and it is still supported.


Extra

Since 4.3 you can use a tagged service locator for this. The configuration is equally simple, but you get the advantage of being able to instantiate the services lazily, instead of having to instantiate all of them to begin with.

You can read more here.

Wednesday, March 31, 2021
 
Nickool
answered 7 Months ago
91

Well, no. (2) old-style ASP.NET webservices is on its way out - it's old, no longer being developed - it's been replaced by WCF.

So this leaves options 1 (straight WCF) and 3 (ADO.NET Data Services - renamed WCF Data Services recently).

Both use WCF as their basic technology - so learning and knowing about WCF is a must in both cases.

With straight WCF (Option 1) you have more options - you can self-host, host your service in IIS, use different protocols and bindings and so on. But with choice comes complexity - you need to learn and know all that stuff - at least to some degree. Your client needs to be able to talk SOAP to you - just about any language (.NET of course, Java, Ruby, PHP - you name it) can talk SOAP in one way or another.

If you're mostly interested in exposing data from databases onto outside clients, I think WCF Data Services is indeed quite a good choice. It's based on REST, so you can hit your WCF Data Service with a browser and just see what happens. It's quite powerful, and even offer a LINQ client side support - you formulate a LINQ query and this gets translated into your appropriate REST call to your data service.

With WCF Data Services, your client needs nothing but a HTTP stack - even the iPhone has that :-) But with a .NET client, things are of course nicer and more comfortable and more efficient.

I'd say check out the WCF Data Service first and see if that satisfies your needs - and if not, dig deeper into WCF. Also check out WCF Data Service at a glance for an intro.

UPDATE:

Marc, do I understand you correctly that WCF on the server fulfils this requirement? And ADO.NET (WCF) Data services too?

Absolutely. WCF (plain or with Data Services) on the server side does NOT dictate the client in any way, shape or form. You can hook up an iPhone to a WCF Data Service, if you really feel like it :-) WCF has been designed from the ground up to be very interoperable - actually, it's the one platform out there that implements the most WS-* industry standards for cross-platform communications.

Saturday, August 14, 2021
 
Lance
answered 2 Months ago
30

UPDATE: As @Tone mentions in the comments, the original accepted answer below is wrong. It is actually valid (and required) for the outer Content-Type of an MTOM HTTP response header to have the value "multipart/related". This is the header the original poster received:

Content-Type    multipart/related; boundary="MIMEBoundaryurn_uuid_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"; start-info="text/xml"; type="text/xml"; start="<0.urn:uuid:xxxxxxxxxxxxxxxxxxxxxxxxxxxx@apache.org>" 

The real problem seems that the "type" attribute is "text/xml" and not "application/xop+xml".

==============================================

Original answer:

The Content-Type header here is "multipart/related". This is not the right header to send for MTOM response, so I believe this is as server error. As the exception mentions, "MTOM messages must have (content) type 'application/xop+xml'". Try to hard code it on the server, or temporaly change it in some proxy to see that wcf will work (you could also try a wcf custom message encoder, should override one property - ContentType - to return right type). Note there are binary payload (file attachment) standards that allow content type of "multipart/related" but wcf does not support them. However here the message payload uses the tag so I beleive the server does mean to send mtom but does not use the right content-type header.

BTW here is an implementaion for the other attachment standard I mentioned for wcf. it may work for you, the pdf will be available in some property. but on the other hand since the xml uses it might not be valid so you might need to tweak it in an encoder anyway. so the best is to solve the root problem in the server.

Tuesday, August 17, 2021
 
stbamb
answered 2 Months ago
51

It took a while but I got it done! A lot of blog posts were helpful but not end-to-end, so in the spirit of paying it forward, here's my blog post to walk through the process.

Sunday, October 10, 2021
 
Limon
answered 1 Week ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :