Web Services Single Sign On mit Shibboleth und Axis2

Thomas Bayer

Von: Thomas Bayer
Datum: 26.10.2007

Shibboleth ist eine, vom internet2-Konsortium entwickelte Single Sign On Lösung für Web-Ressourcen. Da Shibboleth auf HTTP und der Security Assertion Markup Language kurz SAML basiert, ist Shibboleth auch eine interessante Alternative für kommerzielle IT-Landschaften. Dieser Artikel beschreibt einen einfachen Authenticator für Axis2, der es ermöglicht Shibboleth auch für Web Service Single Sign On einzusetzen.

Shibboleth bietet zwei Profile für die Authentifikation. Das Browser/Artifact Profil vereinfacht die Realisierung von Clients und ist daher für Web Services unserer Erfahrung nach geeigneter, als das Browser/POST Profil. Die hier vorgestellte Lösung basiert deshalb auf dem Browser/Artifact Profil. Um den Authenticator für Web Services einzusetzen, ist eine konfigurierte Shibboleth-Umgebung mit Identity- und Service Provider erforderlich. Wie man Shibboleth einrichtet ist ausführlich im Shibboleth Wiki beschrieben.

Axis2 Web-Anwendung für SSO vorbereiten

Dieser Abschnitt beschreibt wie die Axis2 Web-Anwendung in einen Service Provider integriert werden kann. Der Service Provider basiert auf dem Apache HTTPD Server. Die Konfiguration erfolgt in der zentralen Konfigurations-Datei httpd.conf.

In unserem Beispiel läuft die Axis2 Web-Anwendung im Tomcat Web Container. Daher verbinden wir mit den folgenden Zeilen den Apache Webserver mit Tomcat über den JK-Connector. Details zur Konfiguration finden sich in der Tomcat Connector Dokumentation.

LoadModule jk_module modules\mod_jk-apache-2.2.4.so
JkWorkersFile D:\java\apache-tomcat-6.0.14\conf\jk\workers.properties
JkMount /axis2/* ajp13

Der Apache Web Server wird zum Shibboleth Service Provider, indem das Modul mod_shib geladen wird. Konfiguriert wird der Service Provider über die Datei shibboleth.xml.

LoadModule mod_shib C:\opt\shibboleth-sp\libexec\mod_shib_22.so
ShibSchemaDir C:/opt/shibboleth-sp/share/xml/shibboleth
ShibConfig C:/opt/shibboleth-sp/etc/shibboleth/shibboleth.xml

Jetzt brauchen wir nur noch die Axis2 Web-Anwendung mit Shibboleth zu schützen. Clients müssen sich über Shibboleth authentifizieren und als gültige Benutzer ausweisen um Zugriff auf die Webanwendung zu erhalten.

<Location /axis2>
  AuthType shibboleth
  ShibRequireSession On
  require valid-user
</Location>

Auf Serverseite unterscheidet sich eine Webanwendung mit Webservices in nichts von gewöhnlichen Webanwendungen. Die Konfiguration gleicht sich der Konfiguration jeder anderen JEE Webanwendung.

Authentifizieren von Web Service Clients

Die Authentifikation mit Shibboleth basiert auf clientseitigen HTTP Redirects. Versucht ein Client auf eine geschützte Ressource zu zugreifen, wird der Aufruf auf einen Identity Provider, kurz IdP, umgeleitet. Der Client muss sich dann beim IdP authentifizieren. Dies kann mit einem beliebigen Authentifizierungsmechanismus erfolgen. In unserem Beispiel haben wir die HTTP Basic Authentification mit Usernamen und Passwort verwendet. Nach erfolgreicher Authentifikation am IdP wird der Client erneut auf die geschützte Ressource umgeleitet. Mit der erneuten Anfrage an den Service Provider, kurz SP, übermittelt der Client eine Artifact-ID, die der SP verwendet um beim IdP Informationen über den Client anzufordern. Dieses Verfahren funktioniert mit menschlichen Clients, die von einem plötzlich erscheinendem Login nicht überrascht sind und damit zurecht kommen. Leider wissen Clientbibliotheken der Webserviceimplementierungen mit den Redirects und dem Login nichts anzufangen.

Shibboleth Authenticator für Web Service Clients

Um frei verfügbare Clients über Shibboleth authentifizieren zu können haben wir einen einfachen Authenticator entwickelt, der einen Redirect zum IdP mit anschließender Authentifikation durchführen kann. Nach der Authentifikation wird ein Cookie an den Webservice Client übergeben, danach kann der Client sich wie gewohnt mit dem Service verbinden.

Authentifikation mit Authenticator

Listing 1 zeigt einen Client, der sich über den Axis2BrowserArtifactAuthenticator bei einer Shibboleth Installation authentifiziert. Der hier verwendete Web Service ermittelt zu einer Bankleitzahl das zugehörige Kreditinstitut. Die einzige Änderung gegenüber einem Client ohne Authentifikation ist die Fett gedruckte Zeile. Dem Authenticator wird das Portobjekt mit dem Namen stub sowie Username und Passwort übergeben. Pro Webservice ist nur eine Authentifikation notwendig, ganz gleich wieviele Operationen er auf dem Service aufruft.

BLZServiceStub stub=new BLZServiceStub();
Axis2BrowserArtifactAuthenticator.authenticatePwd(stub, "user", "geheim");		
GetBankDocument req=GetBankDocument.Factory.newInstance();
GetBankType getBank=req.addNewGetBank();
getBank.setBlz("67352565");
System.out.println("Kreditinstitut: "+stub.getBank(req).getGetBankResponse().getDetails().getBezeichnung());
Listing 1: Client mit Browser Artifact Authenticator
Web Services SSO Sequenz Diagramm

Diagramm 1: Ablauf des SSO für Web Services mit Shibboleth

Codebeschreibung des Authenticators

Der Authenticator verwendet den HTTP Client aus den Jakarta Projekten. Da Axis2 den Jakarta HTTP-Client selbst verwendet, sind zum Übersetzen und Ausführen des Authenticators nur die Axis2 Bibliotheken notwendig. Listing 2 zeigt den Quellcode des Authenticators. Zu Beginn wird ein HTTPClient und eine POST-Methode initialisiert. Dem Konstruktor von PostMethod wird die Adresse des Web Service Endpoints mitgegeben. Der Authenticator liest diese Adresse aus dem Port-Objekt stub. Nach dem Senden des POST Requestes wird geprüft ob der SP mit einem Redirect zum IdP antwortet. Aus der Antwort des SP wird die Adresse des IdPs aus dem Location-Header ausgelesen. Da wir wissen, dass der Request auf die Login-Seite des IdPs umgeleitet wird und dieser Daten zur Authentifizierung erwartet, schicken wir mit dem nächsten Request Username und Passwort mit, die in einem Credential Objekt übergeben werden. Der Gültigkeitsbereich wird über AuthScope.ANY für alle folgenden HTTP-Anfragen festgelegt. Der Zugriff auf den IdP erfolgt in Shibboleth gewöhnlich über SSL. Daher ist eine Server-Authentifikation mittels Zertifikat notwendig. Falls das Serverzertifikat nicht von einer Root Zertifizierungsstelle unterschrieben wurde, muss dem Client ein Zertifikat bekannt gemacht werden. Beispielsweise über die Datei cacerts (JRE_HOME\lib\security). Im nächsten Schritt wird der eigentliche GET Request erstellt. Hierzu wird die HTTPMethod authMethod erstellt. Über

method.getResponseHeaders("location")[0].getValue()

wird aus dem Header-Feld location, der Antwort des Servers, die Adresse des IdP, auf den weitergeleitet werden soll ausgelesen. Als nächstes wird versucht den GET Request abzusetzen, schlägt der Request aufgrund falscher Authentifikationsdaten fehl wird eine Fehlermeldung ausgegeben. In der Antwort des Servers ist unter anderem ein Session Cookie enthalten. Dieses Session Cookie dient dem SP in der fortlaufenden Verbindung als Authentifikation, so dass ein erneuter Login bei Anforderung einer zweiten Ressource zu welcher der Benutzer Zugriffsrechte besitzt nicht nötig ist. Dieses Cookie wird vom Server über ein Header-Feld übertragen und kann mittels der Methode

authMethod.getRequestHeader("cookie")

ausgelesen werden. Die Methode setCookie() hängt das Cookie dem stub des Service-Clients an. Hierzu wird eine einfache ArrayList, die Elemente des Typs Header enthält, erstellt und das Cookie als einziges Element hinzugefügt. Anschließend wird dem Service-Client des Stubs das Cookie ebenfalls als Header-Feld angehängt.

stub._getServiceClient().getOptions().setProperty(HTTPConstants.HTTP_HEADERS, headers)

Der Webservice Client erhält jetzt mit Hilfe des Session Cookies Zugriff auf alle Ressourcen, die für ihn freigegeben sind , ohne sich erneut authentifizieren zu müssen.

Fazit

Die hier gezeigte Lösung ermöglicht mit wenig Aufwand einen unternehmensweiten oder sogar organisationsübergreifenden Zugriffsschutz mit Single Sign On für Webservices. Bestehende Webservice Clients können mit einer einzigen Zeile Code, welche den Authenticator aufruft, auf eine Authentifizierung mit Shibboleth umgestellt werden. Auf Serverseite muss die Webanwendung, welche die Serviceimplementierung enthält, nicht verändert werden. Um einen Service mit Shibboleth zu schützen, genügt es den Service auf einem Service Provider zu installieren. Der hier vorgestellte Authentikator kann leicht für JAX-WS Clients oder andere Web Service Implementierungen umgeschrieben werden.

26.10.2007, Thomas Bayer, Marco Hippler

Quellcode Authenticator

/**
 * Dieser Code kann beliebig verwendet werden. 
 * Die Autoren übernehmen keine Garantie und keinerlei Haftung.
 *
 * @author Thomas Bayer, Marco Hippler
 *
 */
package com.thomas_bayer.shibboleth;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.SSLHandshakeException;

import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;

import com.thomas_bayer.blz.stub.BLZServiceStub;

public class Axis2BrowserArtifactAuthenticator {

    public static void authenticatePwd(BLZServiceStub stub, String user, String password)
            throws IOException, HttpException {
        HttpClient client = new HttpClient();
        HttpMethod method = new PostMethod(stub._getServiceClient().getOptions().getTo().getAddress());

        int status = client.executeMethod(method);

        if ((status >= 300) && (status < 400)) {
            client.getState().setCredentials(AuthScope.ANY,
                    new UsernamePasswordCredentials(user, password));
            HttpMethod authMethod = new GetMethod(method
                    .getResponseHeaders("location")[0].getValue());
            try {
                client.executeMethod(authMethod);
            } catch (SSLHandshakeException e) {
                throw new RuntimeException(
                        "Server Zertifikat konnte nicht verifiziert werden. ");
            }
            setCookie(authMethod.getRequestHeader("cookie"), stub);
        } else {
            throw new RuntimeException("Keine Weiterleitung zum IdP erhalten.");
        }
    }

    public static void setCookie(Header cookie, BLZServiceStub stub) {
        List<Header> headers = new ArrayList<Header>();
        headers.add(cookie);

        stub._getServiceClient().getOptions().setProperty(
                HTTPConstants.HTTP_HEADERS, headers);
    }

}
Listing 2: Axis2BrowserArtifactAuthenticator

Quellen:

Shibboleth Wiki: https://spaces.internet2.edu/display/SHIB/WebHome

Tomcat connector: http://tomcat.apache.org/connectors-doc/