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.

Initialisierung des Soap Clients
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.

Security Token Service Header Klasse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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:

Header für den Request setzen
1
2
3
4
5
6
7
$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:

Objekt für den Request Body
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$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.

Aufruf der Webservice Methode
1
$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:

Gesendeter Request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
< ?xml version="1.0" encoding="UTF-8"?>
<soap -ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:ns1="http://schemas.xmlsoap.org/ws/2005/02/trust"
    xmlns:ns2="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    xmlns:ns3="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT">
    </soap><soap -ENV:Header>
        <ns2:security SOAP-ENV:mustUnderstand="1">
            <ns2:usernametoken>
                <ns2:username>username</ns2:username>
                <ns2:password>password</ns2:password>
            </ns2:usernametoken>
        </ns2:security>
        <ns3:to>http://localhost:8081/pos_root/services/STService-2.0.0.4</ns3:to>
        <ns3:messageid>urn:uuid:6FAC3CCCFA7B71DE521205308168604</ns3:messageid>
        <ns3:action>RequestSecurityToken</ns3:action>
    </soap>
    <soap -ENV:Body>
        <ns1:requestsecuritytoken>
            <ns1:tokentype>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</ns1:tokentype>
            <ns1:requesttype>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</ns1:requesttype>
        </ns1:requestsecuritytoken>
    </soap>

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:

Janitos Security Token Soap Client Klasse
1
2
3
4
5
6
7
8
9
10
11
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

About Author: Marcel
Ich bin Senior PHP Developer bei MM Newmedia. Seit 2005 bin ich begeisterter Webentwickler und arbeite als Freelancer für namenhafte Firmen und entwickle jede Menge abgefahrenes Zeug und berichte darüber in meinem Blog.

10 thoughts on “PHP5 und BiPRO Teil 4: Security Token Service

  • Pingback: PHP5 Und BiPRO Teil 5: Norm 410 | Der Entwickler Blog Von MM Newmedia

  • Hallo Marcel,

    hast du auch schon mal einen STS mit Zertifikat (x509-Token) in PHP umgesetzt?
    Stehe gerade vor dem Problem…

    VG
    Sven.

    • 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.

  • 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.

    • 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

  • 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.

  • Ganz interessant in diesem Zusammenhang sind auch die Libraries: https://github.com/robrichards/wse-php

    • 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.

  • Hi, eine Lösung bzgl. X509 in Kombination mit pfx.-Zertifikaten wäre die Bombe 🙂
    Danke

  • 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

Kommentar verfassen