Du möchtest LDAP in dein Laravel-Projekt integrieren? Kein Problem. Mit LDAP baust du ein schlankes und effizientes Authentifizierungssystem auf.
Wir stellen dir in diesem Artikel vor, was sich hinter den Begriffen LDAP, OpenLDAP und ActiveDirectory verbirgt und erklären Schritt für Schritt, wie genau du LDAP in deine Laravel Application mithilfe von LdapRecord einbindest.
Was ist LDAP?
LDAP steht für Lightweight Directory Access Protocol. Dabei handelt es sich um ein Protokoll, mit dem du Daten speichern und durch diese navigieren kannst. Die Daten werden, im Gegensatz zu relationalen Datenbanken, die auf Tabellen basieren, in einer Baumstruktur gespeichert. Diese ist vergleichbar mit dem Verzeichnissystem auf einem Computer. Die Datenstruktur erlaubt es, sämtliche Daten in nur einem einzelnen zentralen Verzeichnis zu speichern und diese dennoch kosteneffizient abzufragen.
Da es sich bei LDAP ausschließlich um ein Protokoll handelt, ist es nicht anwendungs- oder systemspezifisch.
Ein LDAP-Verzeichnis besteht aus folgenden Elementen:
- dc – Domain Component. Der Einstiegspunkt der Baumstruktur, vergleichbar mit root in Linux-Systemen
- ou – Organizational Unit. z.B: Abteilungen, Teams etc. Von diesen können beliebig viele ineinander geschachtelt werden.
- cn – Common Name. Die eigentlichen Datensätze, z.B: Personen, Dateien etc.
Durch diese Einträge ist eine jede Entität eindeutig beschreibbar. Von rechts nach links gelesen, navigierst du das Verzeichnis von der Top-Level-Domain bis zum Datenknoten: Die Abfrage der Entität “lisa” könnte in diesem Fall wie folgt aussehen:
cn=lisa,ou=dev,ou=user,dc=server,dc=com
Wann ist LDAP sinnvoll?
Der große Vorteil von LDAP liegt in seiner Einfachheit. Es ersetzt keine aufwendigen Datenbankstrukturen und ist dafür auch gar nicht vorgesehen. Wir empfehlen die Verwendung von LDAP für einfache Daten mit wenig logischer Verknüpfung. LDAP arbeitet am besten mit vielen kleineren, anstatt mit wenigen großen Einträgen. Diese können an zentraler Stelle gespeichert, aber von vielen Providern mit geringem Aufwand angepasst oder aufgerufen werden.
Die klassische Anwendung dafür ist ein Nutzerverzeichnis. Hier wird mit vielen Datensätzen gearbeitet, die wenig Verknüpfungen zueinander haben. Ein Nutzer kann zu einer Gruppe gehören (beispielsweise Admins oder Klienten) und es können Daten zur Authentifizierung hinterlegt sein. Bei Nutzersystemen, deren Komplexität weit darüber hinaus geht, lohnt es sich, umfangreichere Daten in eine relationale Datenbank auszulagern und LDAP primär für den Authentifizierungsschritt zu nutzen.
OpenLDAP und ActiveDirectory
Während das Protokoll von LDAP einheitlich ist, arbeiten Implementierungen oft auch mit abweichenden Verzeichnisstrukturen. Da das Auffinden von Einträgen in LDAP über die Verzeichnisstruktur geschieht, ist das ein entscheidender Unterschied. Die beiden Implementierungen, die dir vermutlich am häufigsten begegnen werden, sind OpenLDAP und Active Directory.
Bei OpenLDAP – wie der Name schon suggeriert, – handelt es sich um Open Source, weshalb es weitreichend Verwendung findet. Das Projekt basiert auf Quellcodes, die von der University of Michigan (den Erfindern des LDAP-Protokolls) bezogen wurden. Damit ist OpenLDAP sehr nahe an der Quelle der LDAP-Entwicklung.
Active Directory (AD) ist ein von Microsoft entwickelter Service. AD basiert zwar auf LDAP, erweitert dieses aber um weitere Komponenten wie DNS. Der Funktionsumfang ist somit etwas umfangreicher als bei OpenLDAP, dafür handelt es sich jedoch nicht um “reines” LDAP.
LDAP und Laravel: LdapRecord!
PHP bietet von Haus aus Funktionalitäten zum Arbeiten mit LDAP. Das ist jedoch leider etwas umständlicher. Zum Glück aller Laravel-Entwickler*innen gibt es mit LdapRecord ein Package, das die Arbeit vereinfacht. Dieses implementiert LDAP mit der gewohnten Model-Struktur von Eloquent, wobei jede Object Class aus LDAP einem Model in Laravel entspricht.
Das heißt, sobald du die Verbindung zu LDAP einmal konfiguriert hast, kannst du problemlos und in gewohnter Manier mit Eloquent arbeiten, ohne dass du viele Gedanken an die Funktionsweise von LDAP im Hintergrund verschwenden musst.
Laravel-Projekt aufsetzen
Als Erstes brauchst du ein Projekt, in dem du arbeiten kannst. Zu diesem Zweck kannst du dein bestehendes Laravel-Projekt verwenden, doch lohnt es sich, es für den Anfang in einem neuen Projekt auszuprobieren. Wir erstellen ein Projekt mit dem passenden Namen “ldap-test”:
composer create-project laravel/laravel ldap-test
Im Projekt selbst solltest du deine Datenbank konfigurieren und die initiale Migration durchführen:
php artisan migrate
LdapRecord konfigurieren
Wie bereits angekündigt, arbeiten wir mit dem Package LdapRecord, welches von DirectoryTree bereitgestellt wird. Dieses installierst du am einfachsten über Composer:
composer require directorytree/ldaprecord-laravel
Nach dem Ausführen des Befehls sollte die Datei config/ldap.php vorhanden sein.
Falls nicht, kann Artisan diese Datei vom Vendor in dein Projekt übertragen:
php artisan vendor:publish --provider="LdapRecord\Laravel\LdapServiceProvider"
Wie oben im Screenshot zu sehen ist, verwendet Ldap Environment-Variablen, um eine Verbindung zu konfigurieren. Das ist ganz ähnlich zu einer regulären Datenbankverbindung in Laravel.
Da die entsprechenden Variablen noch fehlen, trage diese in deiner .env Datei nach.
Falls du einen eigenen LDAP-Server hast, kannst du die Werte an deine Bedürfnisse anpassen.
Zum Zweck dieser Anleitung bedienen wir uns im Folgenden des frei verfügbaren Servers von forumsys.com. Dieser bietet dir nur begrenzten Spielraum, da die dort bereitgestellten Benutzer keine Schreibrechte besitzen. Nichtsdestotrotz hast du damit die Möglichkeit, ohne große Vorarbeit schnell in LDAP einzusteigen, erste Erfahrungen zu sammeln und Tests laufen zu lassen.
LDAP_LOGGING=true
LDAP_CONNECTION=default
LDAP_HOST=ldap.forumsys.com
LDAP_USERNAME="cn=read-only-admin,dc=example,dc=com"
LDAP_PASSWORD=password
LDAP_PORT=389
LDAP_BASE_DN="dc=example,dc=com"
LDAP_TIMEOUT=5
LDAP_SSL=false
LDAP_TLS=false
Sobald deine Konfiguration und Environment-Variablen eingerichtet sind, ist es Zeit für einen ersten Testlauf. Das geht am einfachsten direkt über die Konsole:
php artisan ldap:test
Sollte alles so weit stimmen und Laravel in der Lage sein eine Verbindung mit dem LDAP-Server aufzubauen, bekommst du eine entsprechende Ausgabe.
Models
Nun kannst du in deinem Projekt bereits mit Objekten aus LDAP arbeiten. Hier kommt die große Stärke von LdapRecord zum Tragen. Damit lassen sich nämlich Einträge aus LDAP direkt mit Eloquent aufrufen, wie du das aus Laravel gewohnt bist.
Hierbei ist zu beachten, dass LdapRecord spezifische Models für verschiedene Arten von LDAP-Verzeichnissen (wie zum Beispiel OpenLDAP, ActiveDirectory oder FreeIPA) zur Verfügung stellt. Stelle beim Import der Models also sicher, dass du die Richtigen verwendest! In unserem Beispiel arbeiten wir mit OpenLDAP:
use LdapRecord\Models\OpenLDAP\User;
//...
$users = User::all();
Von Haus aus bietet LdapRecord OpenLDAP-Models für Entry, Group, OrganizationalUnit und User an. Entry ist dabei eine generische Klasse, die alle LDAP-Entitäten umfasst. Group und OrganizationalUnit leiten sich direkt aus der Struktur von OpenLDAP ab.
Welche Models zur Verfügung stehen, hängt von deiner gewählten Verzeichnisstruktur ab. So hast du bei ActiveDirectory noch einiges an zusätzlichen Models.
Darüber hinaus besteht die Möglichkeit, eigene Models anzulegen.
php artisan make:ldap-model LdapUser
Das Model wird unter App\Ldap angelegt und erbt von LdapRecord\Models\Model. Da dies allerdings eine Erweiterung des Models User sein soll, solltest du das extends Statement entsprechend anpassen:
Es lohnt sich, einen Blick in die Klasse zu werfen, die du hier erweiterst. Beispielsweise besitzt das Model bereits eine Definition für $objectClasses. Diese sind besonders relevant, da sich darüber der Objekttyp definiert. Im Model User sehen diese so aus:
public static $objectClasses = [
'top',
'person',
'organizationalperson',
'inetorgperson',
];
Daher solltest du das automatisch erstellte Attribut aus der neuen LdapUser-Klasse löschen, um die Objektklassen der vererbten Klasse nicht zu überschreiben. Das Model sieht dann etwas leer aus und bietet keinen wirklichen Mehrwert im Vergleich zu der Elternklasse. Solltest du aber weiter damit arbeiten, kannst du die Klasse mit eigenen Methoden erweitern und an dein Projekt anpassen.
Authentifizierung
In Laravel wird Authentifizierung über Guards und Provider bewerkstelligt. Diese lassen sich einfach implementieren und konfigurieren, um die Authentifizierung an dein Projekt anzupassen. Mehr dazu erfährst du in unserem Artikel zum Thema: Laravel: Wie du Sicherheitslücken mit richtiger Authentifizierung verhinderst
Die relevanten Konfigurationen findest du in config/auth.php. Lege dort unter ‚providers‘ einen neuen Provider an. Dieser braucht ldap als Driver und ein LDAP Model, das für die Login-Daten genutzt werden soll. Stelle dann sicher, dass du den neuen Provider auch unter ‚guards‘ für die web-Routen eingetragen hast.
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'ldap',
],
],
'providers' => [
'ldap' => [
'driver' => 'ldap',
'model' => App\Ldap\LdapUser::class,
'rules' => [],
],
],
Hier nutzen wir bereits das Model LdapUser, welches wir im letzten Schritt erstellt hatten. Da bisher nicht viel in unserem Model passiert, könntest du an dieser Stelle ebenso LdapRecord\Models\OpenLDAP\User::class, verwenden. Wichtig ist dabei, das richtige Model entsprechend des Verzeichnisses zu wählen (in diesem Fall OpenLDAP).
Es ist an der Zeit, einen Controller anzulegen, um die Authentifizierung zu testen. Das machst du am einfachsten über Artisan:
php artisan make:controller LdapController
Im neu erstellten Controller legst du nun eine neue Funktion an, beispielsweise loginTest(). Dort legen wir einmal händisch zu Testzwecken die Logindaten fest. Alle verfügbaren Logindaten des Testservers findest du unter dem schon erwähnten Link: forumsys.com.
Wenn du mit deinem eigenen Server arbeitest, stelle sicher, dass du diesen Teil nicht in ein öffentliches Verzeichnis lädst! Sobald deine Tests geglückt sind, solltest du die Klardaten aus deinem Projekt entfernen.
Du kannst nun mithilfe der Laravel Facade einen Authentifizierungsversuch starten. Hier lassen wir uns erst mal nur zurückgeben, ob die Anmeldung erfolgreich war, und wenn ja, welcher Nutzer eingeloggt wurde. Du kannst die Authentifizierung sowohl über die Felder mail als auch uid vornehmen.
'newton@ldap.forumsys.com',
'password' => 'password',
];
if (Auth::attempt($credentials)) {
echo 'Credentials are valid!
';
echo (Auth::user());
} else {
echo 'Authentication failed';
}
}
}
Lege jetzt unter routes/web.php eine neue Route an, mit der wir die Funktion aufrufen und testen können:
Route::get('/ldap/login-test', [LdapController::class, 'loginTest']);
Im Browser kannst du schließlich überprüfen, ob die Anmeldung via LDAP funktioniert. Falls deine Anwendung zu diesem Zeitpunkt noch nicht laufen sollte, starte via Artisan den lokalen Development Server:
php artisan serve
Wenn das geklappt hat, solltest du beim Aufrufen der Route Folgendes angezeigt bekommen:
Mit Datenbank synchronisieren
Im vorangegangen Abschnitt haben wir bei jedem Überprüfen der Login-Daten direkt auf den LDAP-Server zugegriffen. Es ist allerdings auch möglich, die Daten von LDAP automatisch in eine Datenbank zu übertragen. Dies erlaubt uns auch dann Zugriff auf die Nutzerdaten, wenn der LDAP-Server nicht erreichbar sein sollte. Außerdem ist das sinnvoll, wenn wir ständige Zugriffe auf den Server vermeiden wollen. Im Folgenden zeigen wir dir, wie du das erreichen kannst.
Im ersten Schritt verknüpfen wir unseren Laravel-User mit LDAP. Wichtig ist hier die Unterscheidung zwischen den Models in LdapRecord und den Laravel-Models. In diesem Fall reden wir von app/Models/User.php. Dies ist auch der Hauptgrund, warum wir das neu erstellte Model als LdapUser bezeichnet hatten, um so eventuelle Verwechslungen gering zu halten.
Um das User-Model mit LDAP zu verknüpfen, benötigst du ein paar zusätzliche Zeilen in deiner Datenbank. Dafür lässt sich über LdapRecord automatisch eine Migration erstellen, die du dann nur noch ausführen musst. Das Ganze funktioniert mit nur zwei Konsolenbefehlen:
php artisan vendor:publish --provider="LdapRecord\Laravel\LdapAuthServiceProvider"
php artisan migrate
Wichtig ist vor allem das Feld guid. Jedes LDAP-Objekt wird anhand dieser eindeutigen globalen ID erkannt und benannt.
Nun, da die Datenbank erweitert wurde, muss auch die Klasse App\Models\User selbst erweitert werden. Sie benötigt zwei Dinge: Den Trait AuthenticatesWithLdap sowie das Interface LdapAuthenticatable.
use LdapRecord\Laravel\Auth\LdapAuthenticatable;
use LdapRecord\Laravel\Auth\AuthenticatesWithLdap;
class User extends Authenticatable implements LdapAuthenticatable
{
use HasApiTokens, HasFactory, Notifiable, AuthenticatesWithLdap;
//...
Sobald du diese hinzugefügt hast, musst du den Provider anpassen. Diesen hatten wir am Anfang bereits auf LDAP umgestellt und mit deinem neuen Model LdapUser verknüpft. Schließlich benötigst du auch eine Referenz darauf, dass die Authentifizierung nicht nur über LDAP, sondern ebenso über die Datenbank erfolgen soll. Navigiere also zu deinem Provider in config/auth.php und erweitere den Eintrag wie folgt:
'providers' => [
'ldap' => [
'driver' => 'ldap',
'model' => App\Ldap\LdapUser::class,
'rules' => [],
'database' => [
'model' => App\Models\User::class,
'sync_passwords' => true,
'sync_attributes' => [
'name' => 'cn',
'email' => 'mail',
],
],
],
],
Unter sync_attributes lassen sich weitere Felder der Datenbank bestimmen, deren Werte direkt aus LDAP übernommen werden sollen. Die Syntax ist hierbei:
'Feld in Datenbank' => 'Feld in LDAP',
Wenn du dich jetzt über die Testfunktion wie gehabt unter /ldap/login-test “einloggst”, entsteht ein neuer Eintrag in der Datenbank:
Mithilfe von sync_password => true legst du fest, dass das Passwort aus dem LDAP-Verzeichnis ebenfalls in die Datenbank übertragen werden soll. Nur so können sich Nutzer*innen, falls der LDAP-Server nicht erreichbar sein sollte, dennoch mit ihrem gewohnten Passwort einloggen. Bei jedem erfolgreichen Login wird der Datenbankeintrag aktualisiert, beispielsweise im Falle einer Passwort-Änderung. Sollte die Synchronisierung auf false gesetzt sein, wird statt des echten Passworts ein zufälliger Hash in die Datenbank eingetragen. Nach dem Erstellen wird dieser zukünftig nicht weiter verändert oder mit den Daten aus LDAP abgeglichen. Ohne Passwort-Synchronisierung sind zwar Nutzerdaten ohne Zugriff auf den Server abrufbar, eine Benutzerauthentifizierung ist dann allerdings nicht über die Datenbank möglich.
Willst du den Login über die Datenbank ermöglichen, musst du zusätzlich einen Fallback definieren. Zurück im LdapController, passt du also dein Array mit den Logindaten an:
$credentials = [
'mail' => 'einstein@ldap.forumsys.com',
'password' => 'password',
'fallback' => [
'email' => 'einstein@ldap.forumsys.com',
'password' => 'password',
],
];
Zu beachten ist hierbei, dass sich die Feldnamen unter fallback nun auf die Datenbank beziehen und nicht auf LDAP (so heißt das Feld in LDAP mail, in der Datenbank allerdings email). Wenn du auf einer lokalen Datenbank arbeitest, kannst du das nun ganz einfach testen, indem du die Netzwerkverbindung unterbrichst. Ist die Synchronisation richtig eingestellt, sollte der Login-Versuch nach wie vor erfolgreich sein.
Daten importieren
Vielleicht reicht es dir, dass nur Nutzer*innen, die sich tatsächlich eingeloggt haben, auch in der Datenbank repräsentiert werden. Möchtest du jedoch alle Nutzer*innen aus LDAP mit einem Mal übertragen, kannst du dies auch direkt mit folgendem Konsolenbefehl tun:
php artisan ldap:import
Nach dem Ausführen wird noch einmal abgefragt, ob die gefundenen Einträge synchronisiert werden sollen. Wir bestätigen dies per Eingabe.
Es ist relevant zu wissen, dass hierbei aus Sicherheitsgründen keine Passwörter übertragen werden. Um sich mit einem Konto über die Datenbank einloggen zu können, muss vorerst mindestens eine Anmeldung via LDAP durchgeführt werden. Erst dabei wird das Passwort synchronisiert und steht danach auch datenbankseitig zur Verfügung.
Damit hast du LDAP erfolgreich konfiguriert und kannst es nun in deinem eigenen Projekt verwenden! Ein sinnvoller nächster Schritt wäre, eine GUI für den Login einzurichten, beispielsweise mithilfe eines der Laravel Starter Kits.
Fazit
LDAP ist ein starkes Werkzeug zur Datenverwaltung und dank LdapRecord kannst du LDAP sehr komfortabel in dein Laravel-Projekt integrieren. In diesem Artikel haben wir uns auf die Authentifizierung beschränkt, es kann aber auch in zahlreichen anderen Szenarien zur Anwendung kommen. Wir empfehlen definitiv, einen Blick auf das Package und LDAP im Allgemeinen zu werfen!
Wobei können wir dich sonst noch unterstützen?
Neues Projekt
Du hast eine Idee für eine digitale Lösung und suchst einen Partner, der dich begleitet?
Verstärkung für dein Projekt
Du hast bereits eine Anwendung und suchst Verstärkung in der Entwicklung?