PHP5 und BiPRO Teil 4: Security Token Service

Ich habe schon lange nichts mehr in der BiPRO Reihe geschrieben. Der letzte Beitrag dieser Reihe wurde im März 2014 veröffentlicht. Da ich aktuell wieder ein wenig mehr mit der BiPRO Norm zu tun habe, möchte ich Euch heute ein konkretes Beispiel für die Umsetzung des Security Token Webservices nach BiPRO Norm 410 zeigen. Das hier gezeigte Beispiel orientiert sich am Webservice der Janitos Versicherung. Es werden also entsprechende Benutzerdaten benötigt. Die Janitos Versicherung liefert in ihrer Webservice Dokumentation ein JAVA Beispiel mit, welches das für den Request benötigte XML aber als kompletten String benutzt. Das ist aus meiner Sicht eher suboptimal, da ein Soap Client das benötigte XML Schema für den Request voll automatisch erstellen kann. Das hier gezeigte Beispiel ist eher PHP5 objektorientierter Code und kommt vollkommen ohne Strings zur Darstellung des XML Requests aus. Ihr könnt dieses Beispiel gern abwandeln und so benutzen, wie ihr es wollt.

Initialisierung des Soap Clients

Fangen wir einfach mit der Initialisierung des Soap Clients an. Immerhin ist das auch das einfachste an der ganzen Umsetzung.

try {
    // soap client
    $oClient = new SoapClient(
        'sts-wsdl-datei.wsdl',
        array(
            'cache_wsdl' => WSDL_CACHE_NONE,
            'exceptions' => true,
            'soap_version' => SOAP_1_1,
            'trace' => true
        )
    );
} catch (SoapFault $e) {
    // error handling
}

In diesem Codebeispiel wird der Soap Client initialisiert. PHP bringt hierzu sein eigenes SoapClient Objekt mit. Die URL zur WSDL Datei wurde ausgespart, da ihr diese von der Janitos Versicherung mit Euren Zugangsdaten bekommt. Für den Produktivbetrieb empfehle ich die cache_wsdl Option dringend auszusparen oder anders zu setzen. Die Option WSDL_CACHE_NONE ist lediglich für das Testen gedacht, da ihr unter Umständen auch eine von der Janitos mitgelieferte WSDL Datei nutzt, die ihr nach Belieben ändern könnt. Die trace Option erlaubt es gesendete Requests und empfangene Responseses auszugeben, um z.B. ein Logging der gesendeten und empfangenen Daten durchzuführen. Alles natürlich in einem try/catch Block gefasst, um etwaige Fehler einfach auffangen zu können. Eigentlich war das soweit schon alles zur Initialisierung des Soap Clients. Jetzt können wir ans Eingemachte gehen.

Soap Header

Hier wären wir dann schon bei der ersten Besonderheit des Security Token Webservices der Janitos Versicherung. Die Janitos verwendet gleich mehrere Header in einem Request. Zunächst habe ich aber eine Klasse programmiert, die den Security Knoten des Requests abbildet.

class SecurityTokenServiceHeader extends SoapHeader {
    protected $sWsseNs = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';

    public function __construct($username, $password) {
        $oSecurity = $this->getSecurityNode($username, $password);
        parent::__construct($this->sWsseNs, 'Security', $oSecurity, true);
    }

    protected function getSecurityNode($username, $password) {
        $oUsernameToken = new stdClass();
        $oUsernameToken->Username = new SoapVar(
            $username, 
            XSD_STRING, 
            null, 
            null,
            'Username',
            $this->sWsseNs
        );
        $oUsernameToken->Password = new SoapVar(
            $password,
            XSD_STRING,
            null,
            null,
            'Password',
            $this->sWsseNs
        );

        $oSecurity = new stdClass();
        $oSecurity->UsernameToken = new SoapVar(
            $oUsernameToken,
            SOAP_ENC_OBJECT,
            null,
            $this->sWsseNs,
            'UsernameToken',
            $this->sWsseNs
        );

        return new SoapVar(
            $oSecurity,
            SOAP_ENC_OBJECT,
            null,
            $this->sWsseNs,
            'Security',
            $this->sWsseNs
        );
    }
}

Diese Klasse ist eine ganz normale Erweiterung der nativen PHP5 SoapHeader Klasse. Ich verwende sie eigentlich für so gut wie jeden WSSE (Web Service Security) Header. Diese Klasse gibt einfach einen Security Knoten mit entsprechenden Username und Password Credentials wieder. Wie das genau aussieht, werde ich später im XML Schema zeigen, welches wir hier gerade mittels der erzeugten Objekte und dem PHP Soap Client erstellen.

Nun müssen wir nach der Initialisierung des Soap Clients einfach nur noch die entsprechenden Header setzen. Das sieht wie folgt aus:

$aHeaders = [];
$aHeaders[] = new SecurityTokenServiceHeader('username', 'password');
$aHeaders[] = new SoapHeader('http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT', 'To', 'http://localhost:8081/pos_root/services/STService-2.0.0.4');
$aHeaders[] = new SoapHeader('http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT', 'MessageID', 'urn:uuid:6FAC3CCCFA7B71DE521205308168604');
$aHeaders[] = new SoapHeader('http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT', 'Action', 'RequestSecurityToken');
	
$oClient->__setSoapHeaders($aHeaders);

Die Janitos hat insgesamt vier Soap Header für einen STS Request. Diese Header werden hier zunächst in einem Array gesammelt und dann an den Soap Client mittels der __setSoapHeaders() Methode übergeben. Für den ersten Header werden Benutzername und Passwort benötigt. Diese Daten werden von der Janitos Versicherung mitgeteilt. Das wäre dann auch schon alles für die Soap Header.

Der eigentliche Request

Der eigentliche Request wird über die Webservice Methode RequestSecurityToken ausgeführt. Diese Methode ist in der WSDL Datei definiert und verlangt ein Objekt als Parameter, welches in der WSDL Datei nicht so klar definiert ist. Dieses Objekt beinhaltet den Token Type und den Request Type. Dieses Objekt wird wie folgt definiert:

$oRequestSecurityToken = new stdClass();
$oRequestSecurityToken->TokenType = new SoapVar(
    'http://schemas.xmlsoap.org/ws/2005/02/sc/sct',
    XSD_STRING,
    null,
    null,
    'TokenType',
    'http://schemas.xmlsoap.org/ws/2005/02/trust'
);
$oRequestSecurityToken->RequestType = new SoapVar(
    'http://schemas.xmlsoap.org/ws/2005/02/trust/Issue',
    XSD_STRING,
    null,
    null,
    'RequestType',
    'http://schemas.xmlsoap.org/ws/2005/02/trust'
);
	
$oRequestSecurityTokenNode = new SoapVar(
    $oRequestSecurityToken,
    SOAP_ENC_OBJECT
);

Dieses Objekt übergeben wir nun einfach an die Webservice Methode.

$response = $oClient->RequestSecurityToken($oRequestSecurityTokenNode);

Das war ’s auch schon. Die Response Variable enthält nun ein Objekt, welches den Token und die Beginn- als auch die Ablaufzeit der Gültigkeitsdauer des Tokens enthält. Zur Vollständigkeit auch noch mal das XML Schema, welches voll automatisch vom Soap Client anhand der übergebenen Objekte erzeugt wird:

< ?xml version="1.0" encoding="UTF-8"?>

    
        
            
                username
                password
            
        
        http://localhost:8081/pos_root/services/STService-2.0.0.4
        urn:uuid:6FAC3CCCFA7B71DE521205308168604
        RequestSecurityToken
    
    
        
            http://schemas.xmlsoap.org/ws/2005/02/sc/sct
            http://schemas.xmlsoap.org/ws/2005/02/trust/Issue
        
    

Der XML Code ist etwas verstümmelt. Das Code Highlight Plugin ist Schuld!

Zu erwartende Probleme

Natürlich wäre es zu einfach, wenn das Gezeigte ohne Weiteres so funktionieren würde. Bei dem direkten Funktionsaufruf mit der gezeigten Initialisierung des Soap Clients endete der Request in einem Soap Fault, der ganz eiskalt „could not connect to host“ sagte. Obwohl der eben gezeigte Weg technisch einwandfrei ist, scheint der Webservice bei Aufruft der RequestSecurityToken Funktion ein wenig rumzuzicken. Der Grund hierfür ist eine falsche Adresse in der WSDL Datei beim Service Binding. Einerseits könnte man die WSDL Datei ändern. Andererseits kann man aber auch den Soap Client entsprechend abändern.

Folgende Ableitung des SoapClients habe ich hierfür programmiert:

class JanitosStsSoapClient extends SoapClient {
    public function __doRequest($request, $location, $action, $version, $oneWay = 0) {
        return parent::__doRequest(
            $request, 
            'https://portal-a-ab.janitos.de/pos/services/STService-2.0.0.4', 
            'RequestSecurityToken', 
            SOAP_1_1, 
            $oneWay
        );
    }
}

Der Soap Client sollte dann mit dieser Klasse anstatt mit dem oben gezeigten PHP eigenen Soap Client initialisiert werden. Schon hat man das Problem gelöst. Sofern man eine lokale WSDL Datei nutzt, macht es aus meiner Sicht allerdings mehr Sinn mal eben schnell die WSDL Datei zu ändern. Sowohl bei der eigens entwickelten SoapClient Klasse als auch bei einer editierten WSDL Datei muss beachtet werden, dass Test- und Produktivumgebung der Janitos unterschiedliche Adressen haben. An dieser Stelle ein großes Lob an die Janitos, die zur Schnittstellenbeschreibung auch immer die WSDL Datei und alle eingebundenen XSD Dateien mitsenden.

Fazit

Die Janitos liefert zur Realisierung ihres BiPRO Webservices lückenlos alle benötigten Daten aus. Allerdings muss auch an dieser Stelle erwähnt werden, dass die Umsetzung für die Janitos auf PHP5 Basis zwar reibungslos funktioniert, sie aber dennoch eine Umsetzung allein für die Janitos Versicherung bleibt. Lediglich die oben erwähnte SecurityTokenServiceHeader Klasse bleibt für mich einigermaßen wiederverwendbar, da ich sie für etliche andere WSSE Requests (auch außerhalb der BiPRO Norm) benutzen kann. Andere Versicherer nutzen für ihren Security Token Service eine andere Struktur und somit programmiert man hier wieder neu. Dieser Umstand macht die Umsetzung der BiPRO Norm mal wieder sehr arbeitsintensiv. Aber dazu habe ich eigentlich schon in den vorhergehenden Artikeln zur BiPRO Norm genügend gesagt.

Der Janitos Webservice nach BiPRO Normen ist fast schon erschreckend einfach mit PHP5 umzusetzen. Ich hoffe, dass ich Euch ein wenig weiter helfen konnte. Bei Fragen und Anregungen schreibt doch bitte einen Kommentar. Wie in den vorangegangenen Artikeln zur BiPRO Norm werde ich versuchen alle Fragen zu beantworten.

Bildquelle: https://unsplash.com/wengenroad / CC0 Lizenz

14 Gedanken zu „PHP5 und BiPRO Teil 4: Security Token Service“

    • Hallo Sven,

      Du bist schon der zweite diese Woche, der nach einer Lösung mit einem X509 Zertifikat fragt. Mir selbst ist ein BiPRO Webservice mit X509 Zertifikat noch nicht unter gekommen. Aber die öffentlich zugängliche BiPRO Dokumentation zur Norm 410 beschreibt dies eigentlich ziemlich gut. Ich werde mich in den nächsten Tagen mal hinsetzen und ein Beispiel auf Basis der öffentlichen BiPRO Normen programmieren und dieses wie im letzten BiPRO Artikel zum Download bereit stellen.

      Antworten
  1. Die Norm ist mir bekannt. Was mir nicht klar ist, auf welche Weise man die Nachricht (den Body) signieren muss, also Request, Zertifikat und Timestamp. Ein Beispiel wäre natürlich auch super mit einer entsprechenden WsseHeader-Klasse.

    Antworten
    • Ich habe mich gestern Abend noch mal in die Norm eingelesen. Insbesondere der Abschnitt X509 Zertifikate ist ein kleines bisschen verwirrend. Um das ganze mit PHP zu realisieren, muss auf jeden Fall die OpenSSL Erweiterung aktiv sein. Die wird benötigt, um Daten des Zertifikats auszulesen und dann im Soap Header darzustellen. Mitunter ist es dort auch nicht genau erklärt. Aus dem in der Norm beispielhaft dargestellten XML Soap Request geht lediglich hervor, dass ein Anker zum Body (Id Attribute bei entsprechenden Knoten) gesetzt wird. Das wird wohl die Signierung der Nachricht sein. Ich werde den Download zur Norm 410 entsprechend anpassen, so dass auch eine X509 Authentifizierung dargestellt wird. Dazu aber später mehr im entsprechenden Blog Artikel.

      Bis dahin,
      Marcel

      Antworten
  2. Genau, die Dokumentation ist da sehr dürftig… Bin sehr gespannt, danke schon mal! Werde mir Dein Beispiel dann genau anschauen und Dir Feedback geben. VG Sven.

    Antworten
    • Leider gehen auch diese Libraries den Umweg über DomDocument, was normalerweise nicht nötig sein sollte, da das XML Schema vom SoapClient / SoapServer Objekt voll automatisch gebildet wird. Im Grunde genommen reicht der Umgang mit Daten Modellen, die man dann als SoapVar Objekt direkt an die entsprechende Webservice Funktion übergibt.

      Das XML Schema per DomDocument zu erstellen ist grundsätzlich nicht falsch, aber eigentlich unnötige Arbeit, weil SopClient bzw. SoapServer dies von ganz allein tun, wenn man die entsprechenden Parameter übergibt. Es reichen also die entsprechenden Daten Modelle, die von der BiPRO beschrieben werden. Wie das ganze funktioniert, ist im Download unter http://www.mm-newmedia.de/2016/03/php5-und-bipro-teil-5-norm-410/ (Download befindet sich am Ende der Seite) dargestellt. Hier werden sowohl Request als auch Response für einen STS unter Verwendung von Username und Password allein durch Datenmodelle dargestellt. DomDocument kommt hier lediglich zum Einsatz, um die Response etwas detaillierter darszustellen.

      Antworten
  3. Hi,
    Bezueglich der X509 und pfx, du musst das pfx file mittels openssl_pkcs12_read() auslesen um den PublicKey und PrivateKey zu erhalten. Damit kannst du dann mittels der wsse lib das XML signieren, dabei musst du darauf achten, das Du nach dem signieren KEINE Aenderungen am xml string mehr vor nimmst. d.h sobald du nur ein \n oder auch nur ein Leerzeichen einfuegst ist die signatur ungueltig. Am mesten machst Du das innerhalb eines __doRequest.

    Viel Glueck

    Antworten
  4. Hallo Marcel,

    zunächst einmal vielen Dank für Deine ausführlichen Tutorials bzgl. BiPro. Man findet ja tatsächlich wenig Infos im Netz, da ist Dein Blog Gold wert 🙂 Und da du BiPro-Experte bist, möchte ich es mit einer Frage versuchen, an der ich mir schon stundenlang die Zähne ausbeiße. Bei einem aktuellen Projekt bringt bereits der reine Konstrukt des „SoapClient“ eine Exception…und zwar „HTTP request failed! HTTP/1.1 510 Not Extended“.

    $client = new SoapClient($wsdl);

    Hattest du so etwas schon mal? Den SCT konnte ich erfolgreich abholen. Ich habe bereits herausgefunden, dass ein HTTP-Header mit dem SCT erwartet wird, ich habe allerdings keine Idee, wie ich diesen bei der Initialisierung setzen soll. Kannst du mich da in die richtige Richtung schubsen?

    Danke und viele Grüße,
    Frank

    Antworten
    • Hallo Frank,

      zunächst mal vielen Dank für die Blumen. Allein „BiPRO Experte“ geht runter wie Öl, ist aber weit entfernt von der Realität. Ich bin nur ein PHP Entwickler, der sich seit Jahren mit dem BiPRO Thema beschäftigt. Leider habe ich in diesen Jahren noch nie einen 510 HTTP Status Code geliefert bekommen. Normalerweise wird der 510er Status geworfen, wenn ein Request nicht die erforderlichen Bestandteile hat oder vorausgesetzte Regeln nicht befolgt. Allerdings sollte es im Zusammenhang mit BiPRO niemals zu so einem Status Code kommen. Die BiPRO legt in ihren Regeln fest, dass ein Nachrichten Objekt geliefert werden muss, sofern es bei der Verarbeitung eines Requests zu Problemen kommt.

      Ich bin auch ein bisschen skeptisch, da der Fehler laut Deiner Beschreibung ja schon bei der Initialisierung des Soap Clients auftritt. Nach meinem Verständnis dürfte dieser Status Code nur geliefert werden, wenn auch wirklich ein Request gesendet worden ist, der keinen Soap Envelope Knoten enthält oder die Definition für diesen Knoten nicht gegeben ist.

      Die Definition des HTTP Extension Framework weist auf den Status Code 510 hin. Allerdings auch nur im Zusammenhang mit einem Request und nicht direkt bei der Initialisierung des Soap Clients. Kann es sein, dass Du einen Request abfeuerst, der keinen Knoten aus dem Namensraum http://schemas.xmlsoap.org/soap/envelope/ besitzt?

      Der erhaltene Security Token muss nicht bei der Initialisierung des Clients gesetzt werden. Der Security Token muss im Header mittels WSSE Security Knoten gesetzt werden. Wie ein solcher Header aussehen kann, ist hier ganz gut definiert.

      Kannst Du sicher stellen, dass nicht doch ein Request abgefeuert wurde?

      Antworten
  5. Hi Marcel,

    ich hänge gerade seit wochen an einem scheinbar nicht mit SOAP-Klasse lösbaren Problem.

    Derzeit habe ich das Problem Attribute für ein Element in SoapVar einzufügen
    Es betrifft diesen Abschnitt:

    name
    password

    Genauer den Teil:
    Type=“http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText“

    Wenn ich den Abschnitt händisch einfüge, also mit XSD_ANYXML statt XSD_STRING hab ich das Problem, dass der Namespace nicht mehr dynamisch zugewiesen werden kann. Ich will aber sowieso gern nur dynamisch das XML erzeugen ohne Händisch einzugreifen.

    Antworten
    • Hallo Tom,

      hast Du es schon mal mit der Angabe eines Type Attributes für die SoapVar Instanz probiert? Du könntest mal Folgendes probieren:
      $password = ‚bla‘;
      $node = new SoapVar(
      $password,
      XSD_STRING,
      ‚http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText‘,
      null,
      ‚Password‘,
      ‚http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd‘
      );

      Die oben gezeigte Initialisierung erzeugt zwar ein xsi:type Attribut, sollte aber dennoch funktionieren. Alternativ hast Du die Möglichkeit in Deiner SoapClient Instanz in der Methode __doRequest() auf das vom Soap Client bereits erstellte XML zuzugreifen. Hier könntest Du das Passwort Element dann um ein Type Attribut so erweitern, dass es den Anforderung entspricht.

      Antworten

Antworten auf SvenAntwort abbrechen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.