Aus Linux-Magazin 05/2005

LDAP-Benutzerauthentifizierung für Apache 2 und Tomcat

Das Lightweight Directory Access Protocol hat sich bei der zentralen Verwaltung von Benutzerdaten bewährt. Das Prinzip eignet sich auch für den Zugriff auf geschützte Webseiten: Mit etwas Geschick beziehen Apache 2 und Tomcat die Daten zur Benutzeridentifikation von einem LDAP-Server.

Viele Admins setzen auf LDAP[1], um Benutzerdaten zentral zu speichern. Damit vereinheitlichen sie die Authentifizierung am Linux-System, am E-Mail-Server oder am Samba-Verzeichnis. Weniger bekannt ist, dass auch Webserver die Userdaten von dort beziehen können. Dieser Artikel beschreibt die Vorgehensweise für Apache 2[2] und Tomcat[3]. Die einzelnen Schritte für eine erfolgreiche Authentifizierung gegen einen LDAP-Server sind:

  • Ein Anwender greift auf einen geschützten Bereich des
    Webservers zu, dieser fragt Benutzerkennung und Passwort ab.
  • Der Webserver stellt eine initiale Verbindung zum LDAP-Server
    her. Dabei tritt er entweder anonym auf oder verwendet eine eigene
    Webserver-Kennung. Der anonyme Zugang ist zwar einfacher, aber
    weniger sicher, da er freien Lesezugriff auf die Benutzerdaten im
    LDAP-Server voraussetzt.
  • Mit der initialen Verbindung sucht der Webserver nach dem
    Usereintrag, der zur Benutzerkennung passt. Als Kennung verwendet
    er ein eindeutiges Attribut im LDAP-Server, etwa die User-ID oder
    E-Mail-Adresse.
  • Der Webserver startet eine neue Verbindung zum LDAP-Server, in
    der er sich mit den Daten des Benutzers ausweist, also mit dem
    zuvor gefundenen Benutzereintrag und dem vom User eingegebenen
    Passwort.
  • Eventuell prüft der Webserver weitere Bedingungen, zum
    Beispiel zusätzliche Attribute oder die Mitgliedschaft in
    einer Gruppe.
  • War die zweite Authentifizierung erfolgreich, ist der Benutzer
    gültig und der Webserver liefert die Daten aus.

In Apache und Tomcat sind die Routinen für diesen Ablauf bereits fertig implementiert, der Webmaster muss nur noch die Konfigurationsdateien anpassen. Als Basis für die folgenden Beispiele dient ein typischer LDAP-Server wie er in Abbildung 1 zu sehen ist (dargestellt im sehr empfehlenswerten LDAP-Frontend JXplorer[4]). Unter der Domäne »groygroy.de« enthält er Organisationseinheiten, Personen und Gruppeneinträge.

Apache 2

Um seine Benutzer gegen einen LDAP-Server zu authentifizieren, benötigt Apache 2 die drei Module Mod_auth, Mod_ldap und Mod_auth_ldap. Ein Eintrag in der Konfigurationsdatei »loadmodule .conf« steuert, welche Module der Webserver lädt. Einige Distributionen bringen dafür auch praktische Konfigurationstools mit.

Listing 1 zeigt die relevante Konfiguration des Webservers. Der erste Abschnitt (bis Zeile 14) definiert den öffentlich zugänglichen Webbereich. Die Files dafür liegen im Verzeichnis »/srv/www/htdocs« (Zeile 4), der Server liefert sie an jeden Besucher aus. Der nächste Abschnitt beschreibt einen geschützten Mitgliederbereich unter »/mitglieder« (Zeilen 17 bis 34). Die Dateien liegen im Verzeichnis »/srv/www/mitglieder« (Alias-Eintrag in Zeile 17).

LDAP einbinden

Wegen der »AuthLDAPEnabled«-Direktive in Zeile 21 authentifiziert der Webserver jeden Zugriff gegen den LDAP-Server. Damit er die dazu nötigen Daten im LDAP-Verzeichnis einsehen darf, muss sich Apache zunächst selbst authentifizieren. Zu diesem Zweck enthalten die Zeilen 22 und 23 den DN (Distinguished Name) und das Passwort für die initiale Verbindung.

Das Kernstück für die Konfiguration ist die »AuthLDAPUrl«-Anweisung. Sie hat folgendes Format:

AuthLDAPUrl ldap://Host:Port/BaseDN?
  Attributname?Suchtiefe?Filter

Host und Port bestimmen, wo Apache den LDAP-Server findet. Der Base-DN bezeichnet den LDAP-Eintrag, unter dem der Webserver nach den Benutzereinträgen suchen soll. Das gewünschte Objekt muss die Benutzerkennung in dem gegebenen Attribut enthalten. Meist wird dies wie im Beispiel die »uid« sein (Zeile 24), andere Attribute wie »mail« sind ebenso möglich. Die Treffer müssen entweder direkt unter dem Base-DN zu finden sein (Suchtiefe »one«) oder dürfen sich beliebig tief im LDAP-Baum verstecken (Suchtiefe »sub«). Bei Bedarf kann ein Filter die Suche weiter einschränken, im Beispiel betrachtet »(objectclass=person)« ausschließlich Einträge vom Typ »person«.

Die Require-Direktiven in den Zeilen 27 bis 29 beschränken die Menge der gültigen Personen noch weiter, zum Beispiel auf Benutzer mit einer speziellen ID oder einem bestimmten DN. Der untere Abschnitt der Konfiguration (Zeilen 42 bis 59) nutzt das Verfahren, um die Mitgliederdateien zusätzlich unter der Webadresse »/schreiben« (Alias in Zeile 40) per Webdav auszuliefern, und zwar nur für Mitglieder der Gruppe »cn=schreiben,dc=groygroy.de« (Require in Zeile 53). Diese dürfen schreibend auf den Server zugreifen (»Dav on« in Zeile 43).

Tomcat

Tomcat[3] als Webserver zu bezeichnen ist eigentlich eine grobe Untertreibung. Dennoch erfüllt Apaches Servlet-Container meist diesen Zweck: Er liefert Webseiten aus, die er zumindest teilweise per Java-Servlets dynamisch erzeugt. Bei Tomcat sind zwei Dateien für die LDAP-Integration zuständig. Die Konfiguration der Authentifizierungsquellen liegt in »conf/server.xml« (Listing 2), speziell im »Realm«-Tag.

Listing 1:
Apache-2-Konfiguration

01 # Beispielkonfiguration für Apache 2 mit einem geschützten Mitgliederbereich.
02 # Die Daten der Benutzer (inklusive ihrem Passwort) stammen aus einem LDAP-Verzeichnis.
03 
04 DocumentRoot "/srv/www/htdocs"
05 
06 # Der normale Bereich für alle Besucher
07 <Directory "/srv/www/htdocs">
08    Options None
09    Options Indexes MultiViews
10    Options Indexes FollowSymLinks
11    AllowOverride None
12    Order allow,deny
13    Allow from all
14 </Directory>
15 
16 # Zugang nur nach Authentifizierung beim LDAP-Server
17 Alias /mitglieder /srv/www/mitglieder
18 <Location "/mitglieder">
19    AuthName LinuxMagazin
20    AuthType Basic
21    AuthLDAPEnabled on
22    AuthLDAPBindDN "cn=Initiale Verbindung,dc=groygroy.de"
23    AuthLDAPBindPassword initial
24    AuthLDAPURL "ldap://digitus:10389/dc=groygroy.de?uid?sub?(objectclass=person)"
25 
26    # Weitere Anforderungen an den Benutzer
27    Require valid-user # alle gültigen Benutzer
28 #  Require otto klara # nur Benutzer mit ID "otto" oder "klara"
29 #  Require dn cn=Otto Normalverbraucher,o=Personen,dc=groy-groy.de # nur ein bestimmter DN
30 
31    AllowOverride None
32    Order allow,deny
33    Allow from all
34 </Location>
35 
36 # Dieser Bereich steht über Webdav auch zum Schreiben
37 # für alle Mitglieder der Gruppe "schreiben" bereit
38 
39 DavLockDB "/tmp/DavLock"
40 Alias /schreiben /srv/www/mitglieder
41 
42 <Location "/schreiben">
43    Dav on
44    ForceType text/plain
45 
46    AuthName LinuxMagazin
47    AuthType Basic
48    AuthLDAPEnabled on
49    AuthLDAPURL "ldap://digitus:10389/dc=groygroy.de?uid?sub?(objectclass=person)"
50    AuthLDAPBindDN "cn=Initiale Verbindung,dc=groygroy.de"
51    AuthLDAPBindPassword initial
52 
53    Require group cn=schreiben,dc=groygroy.de
54 
55    Options none
56    Order allow,deny
57    Allow from all
58    Options +Indexes
59 </Location>

Gruppen inklusive

Anders als Apache 2 wertet Tomcat neben den Benutzereinträgen immer auch die LDAP-Gruppen aus, die der Server als so genannte Role der Java-Seite zur Verfügung stellt. Mit diesen Rollen lassen sich die Zugriffe auf eine Webanwendung kontrollieren, das Verfahren erinnert an die Gruppenzugehörigkeit beim Linux-Dateisystem.

Die drei Attribute »connectionURL«, »connectionName« und »connectionPassword« (Zeilen 3 bis 5) bestimmen den LDAP-Server und die Daten für die initiale Verbindung. Die nächsten drei Attribute (Zeilen 6 bis 8, sie beginnen mit »user«) enthalten die Suchbasis für die Personensuche, die Suchtiefe sowie das Attribut mit der Benutzerkennung. Die verbleibenden vier Attribute (Zeilen 9 bis 12, »role…«) bezeichnen die Suchbasis für die LDAP-Gruppen, die maximale Suchtiefe sowie die Namens- und Mitgliederattribute für die Gruppen.

Abbildung 1: Das universelle LDAP-Frontend JXplorer zeigt links die Struktur des LDAP-Baums und rechts den Inhalt der »lesen«-Tabelle. Apache und Tomcat nutzen diese Daten für ihre Zugangskontrollen.

Abbildung 1: Das universelle LDAP-Frontend JXplorer zeigt links die Struktur des LDAP-Baums und rechts den Inhalt der »lesen«-Tabelle. Apache und Tomcat nutzen diese Daten für ihre Zugangskontrollen.

Listing 2: Tomcat mit
LDAP

01 <Realm
02    className="org.apache.catalina.realm.JNDIRealm"
03    connectionURL="ldap://digitus:10389"
04    connectionName="cn=Initiale Verbindung,dc=groygroy.de"
05    connectionPassword="initial"
06    userBase="dc=groygroy.de"
07    userSubtree="true"
08    userSearch="(uid={0})"
09    roleBase="dc=groygroy.de"
10    roleSubtree="false"
11    roleName="cn"
12    roleSearch="(uniqueMember={0})"
13 />

Identität nutzen

Der geschützte Mitgliederbereich in der Tomcat-Webapplikation soll – wie bereits im Apache-Beispiel – nur für eine bestimmte Benutzergruppe zugänglich sein. Listing 3 zeigt eine passende Konfiguration in der Datei »WEB-INF/web .xml«. Das »security-constraint«-Tag (Zeilen 9 bis 25) beschreibt die URL des geschützten Bereichs (Zeile 14) sowie die zulässigen Rollen (Gruppen) der Benutzer (Zeilen 21 bis 24). Im »login-config«-Tag (Zeilen 28 bis 35) ist angegeben, dass für Login und Fehlerbehandlung die Dateien »login.jsp« und »error.jsp« zuständig sind. Diese beiden JSPs sind zusammen mit den anderen Beispieldateien auf dem FTP-Server des Linux-Magazins[5] zu finden.

Interessanter ist jedoch »index.jsp« in Listing 4. Die Applikation zeigt dem Benutzer einige Informationen zu seiner Kennung. Als Beispiel für eine gut geschriebene JSP-Seite eignet sich diese Datei zwar nicht, sie zeigt aber, was ein JSP mit den Benutzerdaten anfangen kann. Die Java-Seite erhält von Tomcat die Userdaten in einem Principal, das der Java-Code mit »request.getUserPrincipal« abfragt. Während ein Java-Principal gewöhnlich nur die Benutzerkennung enthält, steckt in der Tomcat-Variante zusätzlich das Passwort (Zeile 20).

Abbildung 2: Hat sich der Benutzer angemeldet, erhält das JSP dank Tomcat Zugriff auf Benutzernamen, Passwort und Rollen. Die Authentifizierungsdaten liegen zentral im LDAP-Verzeichnis.

Abbildung 2: Hat sich der Benutzer angemeldet, erhält das JSP dank Tomcat Zugriff auf Benutzernamen, Passwort und Rollen. Die Authentifizierungsdaten liegen zentral im LDAP-Verzeichnis.

Listing 3:
Tomcat-Zugriffsschutz

01 <?xml version="1.0" encoding="ISO-8859-1"?>
02 <web-app xmlns="http://java.sun.com/xml/ns/j2ee"
03    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
05       http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
06    version="2.4">
07 
08    <!-- Alles unter /mitglieder schützen -->
09    <security-constraint>
10       <display-name>Mitglieder-Bereich</display-name>
11       <web-resource-collection>
12          <web-resource-name>Mitglieder</web-resource-name>
13          <!-- Der geschützte Bereich -->
14          <url-pattern>/mitglieder/*</url-pattern>
15          <!-- Die Zugriffsmethoden -->
16          <http-method>DELETE</http-method>
17          <http-method>GET</http-method>
18          <http-method>POST</http-method>
19          <http-method>PUT</http-method>
20       </web-resource-collection>
21       <auth-constraint>
22          <!-- Nur Mitglieder aus einer der Gruppen dürfen rein -->
23          <role-name>lesen</role-name>
24       </auth-constraint>
25    </security-constraint>
26 
27    <!-- Einloggen mit eigener HTML-Seite -->
28    <login-config>
29       <auth-method>FORM</auth-method>
30       <realm-name>Mitglieder Bereich</realm-name>
31       <form-login-config>
32          <form-login-page>/mitglieder/login.jsp</form-login-page>
33          <form-error-page>/mitglieder/error.jsp</form-error-page>
34       </form-login-config>
35    </login-config>
36 </web-app>

Auf Umwegen zum Passwort

Bevor es auf dieses zusätzliche Feld zugreift, fragt das JSP in Zeile 17 zunächst, ob sich überhaupt ein Benutzer angemeldet hat. Der Code speichert das Principal in einem Objekt der Klasse »java .security.Principal«. Zeile 19 vergleicht dann den tatsächlichen Klassennamen des Objekts mit dem eines Tomcat-Principal. Sind sie identisch, fragt Zeile 20 per Reflection-Technik das Passwort ab. Der Code hierfür ist reichlich umständlich, aber leider nötig.

Das Passwort braucht eine Webapplikation etwa dann, wenn sie unter der Kennung des Benutzers eine Verbindung zum LDAP-Server aufbauen will. Damit kann ein Kunde beispielsweise seine Kontaktinformationen pflegen. Einfacher als an das Passwort kommt das JSP an eine Rolle, die Zeilen 28 bis 39 enthalten hierfür ein Beispiel.

Wenn sich der Benutzer von der Webapplikation ausloggen will, muss das JSP die Session löschen. Die Zeile 47 erzeugt einen Abmelden-Link. Der Code in den Zeilen 4 bis 8 wertet den »logoff«-Parameter aus, den ein Link-Parameter an das JSP übergeben hat.

Schutz total

Die beiden Beispiele zeigen, wie eine Webapplikation LDAP zur Authentifizierung der Benutzer verwenden kann. Eine produktiv genutzte Anwendung sollte zusätzlich die initiale Verbindung mit Passwort oder besser mit SSL schützen. Bei erhöhten Sicherheitsanforderungen kann Tomcat auch ein X.509-Zertifikat des Benutzers prüfen. Die Rückruflisten findet er dann ebenfalls im LDAP-Server. (fjl)

Listing 4: Benutzerdaten
auswerten

01 <%@ page contentType="text/html" %>
02 
03 <% // ausloggen
04    if (request.getParameter("logoff") != null) {
05       session.invalidate();
06       response.sendRedirect("index.jsp");
07       return;
08    }
09 %>
10 
11 <html><head>
12   <title>Benutzerkennung auswerten</title>
13 </head><body bgcolor="white">
14 
15 Sie sind als Benutzer <b>«<%= request.getRemoteUser() %>»</b>
16 <% // verwendetes Passwort abfragen
17    if (request.getUserPrincipal() != null) {
18       java.security.Principal p = request.getUserPrincipal();
19       if (org.apache.catalina.realm.GenericPrincipal.class.getName().equals( p.getClass().getName() )) {
20          String credential = (String) p.getClass().getMethod("getPassword", null).invoke(p, null);
21             out.println( "mit dem Passwort <b>«" + credential + "»</b>" );
22       }
23    }
24 %>
25 eingeloggt.
26 
27 <p>
28 <% // Rolle überprüfen
29    String role = request.getParameter("role");
30    if (role == null)
31       role = "";
32    if (role.length() > 0) {
33       if (request.isUserInRole(role)) {
34          out.println( "Sie sind Mitglied in der Gruppe <b>«" + role + "»</b>");
35       } else {
36          out.println( "Sie sind nicht Mitglied in der Gruppe <b>«" + role + "»</b>");
37       }
38    }
39 %>
40 <p>
41 
42 <form method="GET" action='<%= response.encodeURL("index.jsp") %>'>
43    Hier prüfen Sie, ob Sie Mitglied einer Gruppe sind:
44    <input type="text" name="role" value="<%= role %>">
45 </form>
46 <p>
47 <a href='http://<%=%20response.encodeURL(index.jsp?logoff=true)%20%>'>Ausloggen</a>.
48 </body></html>

Infos

[1] OpenLDAP: [http://www.openldap.org]

[2] Apache 2: [http://httpd.apache.org]

[3] Apache Tomcat: [http://jakarta.apache.org/tomcat/]

[4] JXplorer: [http://www.jxplorer.org]

[5] Alle Dateien: [ftp://ftp.linux-magazin.de/pub/magazin/2005/05/Apache-LDAP/]

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben