Heute habe ich zum ersten Mal versucht, einen PHP SOAP WebService im Visual Studio 2008 einzubinden, der mittels Basic Authentifizierung gesichert ist. Ich hätte erwartet, dass das Visual Studio den Proxy korrekt einrichtet und auch die app.config anpasst, aber leider funktionierte es trotzdem nicht.

Der Service wurde eingerichtet und ich wollte ihn aufrufen:

ServiceReference1.ServicePortTypeClient s = new ServiceReference1.ServicePortTypeClient();
s.GetAnswer("Hello");

Wie erwartet kam ein Fehler, dass die Authentifizierung nicht funktioniert, also habe ich User/Password mit übergeben:

.ClientCredentials.UserName.UserName = "user";
s.ClientCredentials.UserName.Password = "pwd";

// alternativ:

s.ChannelFactory.Credentials.UserName.UserName = "user";
s.ChannelFactory.Credentials.UserName.Password = "pwd";

Leider war das auch noch nicht die Lösung. Jetzt kam eine MessageSecurityException, dass das Authentication Scheme "Anonymous" nicht genutzt werdn konnte, sondern Basic angefordert wurde. Ist ja auch klar, das Visual Studio hatte mich bei der Proxy Erzeugung ja auch schon nach Benutzer und Passwort gefragt und dort korrekt die Basic Authentifizierung durchgeführt. Anscheinend hat sich das im erstellen Proxy nur nicht ausgewirkt. Der genaue Exception Text war ("Sicherheitsbereich" ist der auf Apache-Seite vergebene Name in der .htaccess):

The HTTP request is unauthorized with client authentication scheme ‘Anonymous’. The authentication header received from the server was ‘Basic realm=\"Sicherheitsbereich\"’.

Also scheint irgendwas mit dem Binding, welches in der app.config definiert ist, nicht zu stimmen. Standardmäßig wird der folgende Client und Binding angelegt:

<configuration> 
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="ServiceSoapBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="None">
            <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security> 
        </binding> 
      </basicHttpBinding> 
    </bindings> 
    <client> 
      <endpoint address="http://example.com/service.php" binding="basicHttpBinding" bindingConfiguration="ServiceSoapBinding" contract="ServiceReference1.ServicePortType" name="ServicePort1" />
    </client> 
  </system.serviceModel>
</configuration>

Das verwendete Binding ist ein basicHttpBinding, wobei das "Basic" nichts mit dem Authentication Scheme "Basic" zu tun hat… Kurz gesagt ist das Binding falsch und es muss ein neues her. Hierfür kann man sich ein customBinding anlegen, welches das Authentication Scheme "Basic" versteht (hier nur ein Minimalbeispiel):

<customBinding>
  <binding name="MyBasicAuthenticationSchemeSoapBinding">
    <textMessageEncoding messageVersion="Soap11" />
    <httpTransport authenticationScheme="Basic" keepAliveEnabled="false" proxyAuthenticationScheme="Basic" />
  </binding>
</customBinding>

Jetzt muss man dem Client nur noch beibringen, dass er den neuen Client verwenden soll. Hierzu muss "binding" und "bindingConfiguration" angepasst werden:

<client> 
  <endpoint address="http://example.com/service.php" binding="customBinding" bindingConfiguration="MyBasicAuthenticationSchemeSoapBinding" contract="ServiceReference1.ServicePortType" name="ServicePort1" />
</client>

Jetzt sollte der Aufruf, sofern Benutzername und Passwort korrekt angegeben ist, problemlos funktionieren und die Kommunikation kann starten.

Ein kleiner Nachtrag noch: Wenn "keepAliveEnabled" im Binding auf "true" gesetzt ist, gibt es im Apache Fehlermeldungen, weil die URL falsch aufgerufen wurde. In der .NET Applikation wurde bei mir eine 400 (Bad Request) geschickt.