Adventskalender 2025 – Minimaler Login Prozess – Teil 2

Sven Ruppert

Was bisher geschah..

Im ersten Teil von „Basic Login Solution“ wurde die Grundlage für einen bewusst einfachen, aber strukturell sauberen Admin-Login gelegt. Ausgangspunkt war die Erkenntnis, dass selbst ein technisch schlanker URL-Shortener eine klare Trennung zwischen öffentlichen Funktionen und administrativen Eingriffen benötigt. Ziel war dabei keine vollständige Benutzerverwaltung, sondern eine minimale Zugriffsschranke, die sich nahtlos in die bestehende Core-Java- und Vaadin-Architektur einfügt.

Zentraler Baustein dieser Lösung ist eine schlanke, dateibasierte Konfiguration über auth.properties. Mit nur zwei Parametern – einem Aktivierungsschalter und einem Passwort – lässt sich der Login vollständig steuern. Der zugehörige LoginConfigInitializer stellt sicher, dass diese Konfiguration bereits beim Start des Servlet-Containers eingelesen wird und der Anwendung konsistent zur Verfügung steht. Damit ist klar definiert, ob und wie der Schutzmechanismus greift, noch bevor irgendeine UI gerendert wird.

Darauf aufbauend wurde eine eigenständige Login-View eingeführt, die bewusst ohne MainLayout auskommt. Sie bildet den klaren Eintrittspunkt in den administrativen Bereich und trennt visuell wie technisch den Login-Kontext vom restlichen UI. Die Implementierung fokussiert sich auf ein reduziertes Benutzererlebnis: ein Passwortfeld, ein klarer Call-to-Action und eindeutige Rückmeldungen bei Erfolg oder Fehlschlag. Gleichzeitig zeigt sich hier bereits ein wichtiges Architekturprinzip dieses Tages: Sicherheitslogik und UI-Interaktion bleiben sauber voneinander getrennt.

Nach Abschluss von Teil 1 ist damit zwar ein funktionaler Login vorhanden – jedoch noch keine echte Zugriffskontrolle. Ohne zusätzliche Maßnahmen könnten nicht authentifizierte Benutzer weiterhin direkt auf administrative Views zugreifen, etwa über Deep Links oder gespeicherte URLs. Genau an dieser Stelle setzt der zweite Teil an.

In Part 2 wird der Login erstmals wirksam durchgesetzt: über einen zentralen Route-Schutz im MainLayout, eine konsistente Sitzungsverwaltung mit SessionAuth und eine saubere Logout-Mechanik. Damit wird aus einem isolierten Login-Screen ein vollständiger, durchgängiger Authentifizierungsfluss, der den administrativen Bereich der Anwendung zuverlässig abschirmt.

Der Quelltext zu diesem Artikel befindet sich auf GitHub unter der folgenden URL: https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-09

Routenschutz & Zugriffslogik

Mit der Einführung des Admin-Logins reicht es nicht aus, lediglich eine Login-Seite bereitzustellen. Entscheidend für die Wirksamkeit der Absicherung ist, dass sämtliche administrativen Views konsequent geschützt werden und nur für authentifizierte Benutzer zugänglich sind. Genau an dieser Stelle setzt der Route-Schutz an, der im Zusammenspiel mit LoginConfig und SessionAuth dafür sorgt, dass die Anwendung klar zwischen angemeldeten und nicht angemeldeten Nutzern unterscheidet.

Statt jede einzelne View separat abzusichern, nutzt die Implementierung eine zentrale Stelle: das MainLayout. Da alle relevanten Verwaltungs-Views dieses Layout verwenden, bietet sich hier ein natürlicher Ankerpunkt, um die Zugriffslogik zu bündeln. Sobald eine View mit diesem Layout navigiert werden soll, kann das MainLayout prüfen, ob der Login überhaupt aktiviert ist und ob der aktuelle Benutzer bereits als authentifiziert gilt. Erst wenn diese Bedingungen erfüllt sind, wird der Zugriff auf die Ziel-View zugelassen.

Die Entscheidung, den Route-Schutz über das Layout umzusetzen, hat mehrere Vorteile. Zum einen bleibt der Code in den einzelnen Views schlank, weil sie sich nicht selbst um Sicherheitsprüfungen kümmern müssen. Zum anderen entsteht eine einheitliche Logik, die alle geschützten Bereiche gleichermaßen erfasst. Wird der Mechanismus später erweitert oder angepasst, genügt eine Änderung an einer zentralen Stelle, um das Verhalten in der gesamten Anwendung zu beeinflussen.

Funktional betrachtet folgt der Route-Schutz dabei einem einfachen Ablauf: Kommt eine Navigation in eine View, die das MainLayout verwendet, wird vor dem eigentlichen Rendering geprüft, ob der Login global aktiviert ist. Ist dies nicht der Fall, verhält sich die Anwendung wie zuvor und erlaubt weiterhin direkten Zugriff auf alle Seiten. Ist der Login hingegen eingeschaltet, prüft die Logik, ob die aktuelle Sitzung als authentifiziert markiert ist. Falls nicht, wird der Benutzer automatisch zur Login-Seite umgeleitet.

Eine Ausnahme bildet die Login-View selbst. Sie muss grundsätzlich ohne vorherige Authentifizierung erreichbar sein, da sie gerade den Einstieg in den geschützten Bereich ermöglicht. Würde man sie ebenfalls dem Routenschutz unterwerfen, entstünde eine Endlosschleife der Weiterleitungen. Die Implementierung trägt diesem Sonderfall explizit Rechnung, indem sie die Login-Route aus der Prüfung herausnimmt.

In der Praxis führt dieser Mechanismus zu einem klaren und vorhersagbaren Verhalten: Nicht angemeldete Benutzer, die direkt einen Deep Link in die Admin-UI öffnen, landen automatisch auf der Login-Seite. Nach erfolgreicher Anmeldung gelangen Sie zur gewünschten Verwaltungsseite und bewegen sich anschließend ohne weitere Unterbrechungen im geschützten Bereich. Gleichzeitig bleibt die Möglichkeit erhalten, den Login auf Konfigurationsebene vollständig abzuschalten – in diesem Fall verhält sich die Anwendung so, als hätte der Route-Schutz nie existiert.

Implementierung im MainLayout

Die technische Umsetzung des Route-Schutzes ist im MainLayout verankert. Dieses dient nicht nur als visueller Rahmen für die Verwaltungsoberfläche, sondern übernimmt über das Interface BeforeEnterObserver auch eine zentrale Kontrollfunktion für alle Navigationen, die in den geschützten Bereich führen.

Zunächst wird das Layout so erweitert, dass es an der Navigation teilnehmen kann und zugleich Logging-Funktionalität erhält:

public class MainLayout
    extends AppLayout
    implements BeforeEnterObserver, HasLogger {

Durch die Implementierung von BeforeEnterObserver erhält das MainLayout die Möglichkeit, vor jeder Navigation in eine View, die dieses Layout verwendet, eine Prüfung durchzuführen. Gleichzeitig stellt HasLogger eine bequeme Logging-API zur Verfügung, um Entscheidungen und Zustände im Log nachverfolgbar zu machen.

Der eigentliche Route-Schutz ist in der Methode beforeEnter gebündelt:

  @Override
  public void beforeEnter(BeforeEnterEvent event) {

    // If login is globally turned off, do not protect any routes.
    if (!LoginConfig.isLoginEnabled()) {
      return;
    }

    logger().info("beforeEnter target={} authenticated={}",
                  event.getNavigationTarget().getSimpleName(),
                  SessionAuth.isAuthenticated());

    // The login view itself must never be protected, otherwise we create a loop
    if (event.getNavigationTarget().equals(LoginView.class)) {
      return;
    }

    if (!SessionAuth.isAuthenticated()) {
      logger().info("beforeEnter.. isAuthenticated()==false - reroute to LoginView");
      event.rerouteTo(LoginView.class);
    }
  }

Die Logik folgt genau dem zuvor beschriebenen Ablauf. Zunächst wird geprüft, ob der Login gemäß Konfiguration überhaupt aktiv ist. Ist login.enabled in der auth.properties-Datei deaktiviert, kehrt die Methode sofort zurück, ohne irgendeine Einschränkung vorzunehmen. In diesem Modus verhält sich die Anwendung also wie vor der Einführung des Route-Schutzes.

Ist der Login aktiviert, wird als Nächstes geprüft, welches Ziel die aktuelle Navigation hat und ob der Benutzer bereits authentifiziert ist. Ein kurzer Log-Eintrag hält fest, in welche View navigiert werden soll und wie der aktuelle Authentifizierungsstatus aussieht. Handelt es sich beim Ziel um die LoginView, bricht die Prüfung ab – die Login-Seite bleibt immer erreichbar, unabhängig davon, ob die Sitzung bereits angemeldet ist oder nicht.

Für alle anderen Views gilt: Ist die Sitzung laut SessionAuth.isAuthenticated() nicht als angemeldet markiert, wird der Benutzer transparent zur Login-Seite umgeleitet. Die ursprüngliche Ziel-View wird nicht gerendert, sodass keine administrative Funktion ohne vorherige Authentifizierung sichtbar wird.

Logout-Knopf im Header

Eng mit dem Route-Schutz verknüpft ist die Möglichkeit, eine bestehende Sitzung wieder sauber zu beenden. Auch diese Funktionalität ist im MainLayout implementiert und zeigt sich für den Benutzer als Logout-Button im Header. Die Erzeugung dieses Buttons ist an die Konfiguration gekoppelt:

    HorizontalLayout headerRow;
    if (LoginConfig.isLoginEnabled()) {
      var logoutButton = new Button("Logout", _ -> {
        SessionAuth.clearAuthentication();
        UI.getCurrent().getPage().setLocation("/" + LoginView.PATH);
      });
      logoutButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
      headerRow = new HorizontalLayout(toggle, appTitle, new Span(), storeIndicator, logoutButton);
    } else {
      headerRow = new HorizontalLayout(toggle, appTitle, new Span(), storeIndicator);
    }

Ist der Login deaktiviert, wird kein Logout-Button angezeigt, da es in diesem Fall keinen Zustand gibt, der beendet werden müsste. Ist der Login hingegen aktiv, ergänzt das MainLayout die Kopfzeile um einen schlichten, inline dargestellten Logout-Button. Ein Klick darauf ruft SessionAuth.clearAuthentication() auf, entfernt damit das Authentifizierungsattribut aus der VaadinSession und schließt die Sitzung. Anschließend wird der Browser explizit per setLocation zur Login-Seite umgelenkt.

Durch die Kombination aus beforeEnter-Prüfung und Logout-Button entsteht eine konsistente Zugriffslogik: Benutzer werden beim ersten Zugriff in den geschützten Bereich zur Login-Seite geführt, bewegen sich nach erfolgreicher Anmeldung frei innerhalb des Admin-UIs und können ihre Sitzung jederzeit sichtbar und kontrolliert wieder beenden.

Im nächsten Kapitel widmen wir uns der Klasse SessionAuth, die den Login-Status in der Sitzung kapselt und zentral zur Verfügung stellt.

Logout-Funktion im Header

Ein Login-Mechanismus ist nur dann vollständig, wenn er auch einen klaren Weg zurück kennt. In der Praxis bedeutet das: Wer sich am Admin-Interface anmeldet, muss seine Sitzung ebenso bewusst wieder beenden können. Die Einführung einer Logout-Funktion ist daher nicht nur eine technische Ergänzung, sondern auch ein wichtiges Signal an Benutzerinnen und Benutzer, dass der Zugang zum Verwaltungsbereich als sensibler Kontext verstanden wird.

Im URL-Shortener zeigt sich diese Funktionalität als schlanker Logout-Button im Header. Er ist nur sichtbar, wenn der Login-Schutz in der Konfiguration aktiviert ist und die Anwendung sich somit im „geschützten Modus“ befindet. In allen Fällen, in denen „login.enabled=false“ gesetzt ist, verzichtet das UI auf diese Schaltfläche, da es keine Sitzung gibt, die im sicherheitstechnischen Sinne abgemeldet werden müsste. Damit bleibt die Oberfläche in einfachen Entwicklungs- oder Demo-Szenarien bewusst aufgeräumt.

Aus Nutzersicht fügt sich der Logout-Button unauffällig in die bestehende Kopfzeile ein. Er tritt nicht als dominantes CTA-Element auf, ist aber jederzeit erreichbar, sobald man im Admin-Bereich ist. Die Benennung ist bewusst klar gehalten: „Logout“ lässt keinen Interpretationsspielraum und macht deutlich, dass hier die aktuelle Berechtigungssituation beendet wird. In Umgebungen, in denen mehrere Personen nacheinander mit demselben Browser und derselben Admin-Oberfläche arbeiten, ist diese Klarheit besonders wichtig.

Technisch erfüllt der Klick auf den Logout-Button zwei Aufgaben: Zum einen wird der Authentifizierungsstatus in der aktuellen VaadinSession zurückgesetzt, zum anderen wird der Browser aktiv zurück zur Login-Seite weitergeleitet. Damit ist sichergestellt, dass die administrativen Views nicht einfach im Browser geöffnet bleiben, sondern dass der nächste Zugriff wieder über den Login-Fluss erfolgt. Auch ein versehentlich offener Tab verliert damit seine Berechtigung, sobald der Logout durchgeführt wurde.

Die Implementierung hält sich dabei eng an die restliche Login-Architektur. Der Button prüft nicht selbst, ob eine Sitzung authentifiziert ist, sondern delegiert dies an die kleine Hilfsklasse SessionAuth, die auch an anderen Stellen – etwa beim Route-Schutz – verwendet wird. So bleibt die Abmeldung konsistent: Die gleiche Abstraktion, die feststellt, ob eine Sitzung als angemeldet gilt, ist auch dafür zuständig, diesen Status wieder zu löschen.

Aus UX-Perspektive trägt die Logout-Funktion zudem dazu bei, den mentalen Modus der Benutzer zu strukturieren. Solange sie eingeloggt sind, befinden sie sich in einem aktiven Administrationskontext, in dem Änderungen an Kurzlinks und Konfigurationen erwartet werden. Mit dem Logout wechseln sie bewusst zurück in eine neutrale Rolle, in der sie die erzeugten URLs höchstens noch aus Sicht der Anwender nutzen. Diese Trennung ist gerade in heterogenen Teams hilfreich, in denen nicht alle Beteiligten gleichzeitig Administrationsrechte benötigen.

Implementierung im MainLayout

Die Logout-Funktion ist im MainLayout zentral verankert. Dort wird die Kopfzeile aufgebaut, in der neben dem App-Titel und dem Store-Indikator optional der Logout-Button eingefügt wird. Ob dieser Button sichtbar ist, hängt direkt von der Login-Konfiguration ab:

HorizontalLayout headerRow;
if (LoginConfig.isLoginEnabled()) {
  var logoutButton = new Button("Logout", _ -> {
    SessionAuth.clearAuthentication();
    UI.getCurrent().getPage().setLocation("/" + LoginView.PATH);
  });
  logoutButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
  headerRow = new HorizontalLayout(toggle, appTitle, new Span(), storeIndicator, logoutButton);
} else {
  headerRow = new HorizontalLayout(toggle, appTitle, new Span(), storeIndicator);
}

Im aktivierten Modus wird ein Button mit der Beschriftung „Logout“ erzeugt und in den HorizontalLayout des Headers eingefügt. Das Aussehen orientiert sich am restlichen UI und verwendet die Variante LUMO_TERTIARY_INLINE, sodass der Button dezent wirkt und sich optisch in die Kopfzeile einfügt.

Die eigentliche Abmelde-Logik befindet sich im Click-Listener: Zunächst wird SessionAuth.clearAuthentication() aufgerufen, um den Authentifizierungszustand der aktuellen Sitzung zurückzusetzen. Anschließend wird die Browser-Seite explizit auf den Pfad der Login-View umgelenkt. Dadurch gelangt der Benutzer nach dem Logout nicht einfach nur auf eine „leere“ Seite, sondern klar erkennbar zurück zum Einstiegspunkt des Admin-Bereichs.

Ist die Login-Funktion global deaktiviert, entfällt der Button vollständig. Das headerRow-Layout besteht dann nur aus Toggle, Titel, Abstandshalter und StoreIndicator. Damit bleibt das UI in Szenarien ohne Login-Schutz bewusst schlank, ohne eine nicht funktionale Abmeldeschaltfläche anzuzeigen.

SessionAuth – Verwaltung des Sitzungszustands

Die Hilfsklasse SessionAuth umfasst alle Zugriffe auf den Authentifizierungsstatus innerhalb der VaadinSession. Sie wird sowohl vom Route-Schutz als auch vom Logout-Button genutzt und stellt damit die zentrale Abstraktionsschicht für Login-Entscheidungen dar:

package com.svenruppert.urlshortener.ui.vaadin.security;

import com.svenruppert.dependencies.core.logger.HasLogger;
import com.vaadin.flow.server.VaadinSession;

import static com.svenruppert.dependencies.core.logger.HasLogger.staticLogger;
import static java.lang.Boolean.TRUE;

public final class SessionAuth
    implements HasLogger {

  private static final String ATTR_AUTH = "authenticated";

  private SessionAuth() {
  }

  public static boolean isAuthenticated() {
    VaadinSession session = VaadinSession.getCurrent();
    if (session == null) return false;
    var attribute = session.getAttribute(ATTR_AUTH);
    staticLogger().info("isAuthenticated.. {}", attribute);
    return TRUE.equals(attribute);
  }

  public static void markAuthenticated() {
    staticLogger().info("markAuthenticated.. ");
    VaadinSession session = VaadinSession.getCurrent();
    if (session != null) {
      session.setAttribute(ATTR_AUTH, TRUE);
    }
  }

  public static void clearAuthentication() {
    staticLogger().info("clearAuthentication.. ");
    VaadinSession session = VaadinSession.getCurrent();
    if (session != null) {
      session.setAttribute(ATTR_AUTH, null);
      session.close();
    }
  }
}

Die Methode markAuthenticated() wird nach einem erfolgreichen Login aufgerufen und setzt in der aktuellen VaadinSession das einfache Attribut authenticated auf TRUE. Mit isAuthenticated() lässt sich dieser Zustand später erneut abfragen – etwa im Route-Schutz des MainLayout. Beide Methoden nutzen Logging, um im Fehlerfall oder bei der Analyse des Nutzerverhaltens schnell nachvollziehen zu können, wann eine Sitzung angemeldet oder abgemeldet wurde.

Für den Logout ist clearAuthentication() entscheidend. Sie entfernt das Attribut wieder aus der Sitzung und schließt zusätzlich die VaadinSession. Dadurch werden auch andere sitzungsbezogene Daten verworfen, und ein versehentlich geöffneter Browser-Tab verliert seine Berechtigungen. Beim nächsten Zugriff wird eine neue, nicht authentifizierte Sitzung aufgebaut, die anschließend durch den Login-Flow geführt wird.

Durch diese enge Verzahnung von MainLayout und SessionAuth entsteht eine robuste, dennoch überschaubare Logout-Implementierung. Der Header bietet einen klar sichtbaren Ausstieg aus dem Admin-Kontext, und die Sitzungslogik stellt sicher, dass dieser Ausstieg auch technisch konsequent umgesetzt wird.

Im nächsten Kapitel wenden wir uns ausführlicher der Klasse SessionAuth zu und betrachten deren Rolle im Zusammenspiel mit dem Route-Schutz und der Login-View im Gesamtfluss der Anwendung.

SessionAuth: Sitzungsbasierte Authentifizierung im Gesamtfluss

Nachdem der Login-Schutz, die Login-Seite und die Logout-Funktion eingeführt wurden, bleibt eine zentrale Frage: Wo genau wird eigentlich festgehalten, ob ein Benutzer als authentifiziert gilt oder nicht? Die Antwort darauf liegt in der kleinen, aber entscheidenden Hilfsklasse SessionAuth, die den Authentifizierungszustand in der VaadinSession verankert.

Im Kern erfüllt SessionAuth drei Aufgaben. Sie kann zunächst feststellen, ob eine aktuelle Sitzung bereits angemeldet ist. Sie kann zweitens nach erfolgreichem Login die Sitzung als authentifiziert markieren. Und sie kann drittens beim Logout den Authentifizierungsstatus wieder entfernen und die Sitzung schließen. Diese drei Operationen bilden die Grundlage dafür, dass Login-View, Route-Schutz im MainLayout und Logout-Button im Header auf einen gemeinsamen Wahrheitswert zugreifen, statt jeweils eigene Mechanismen zu pflegen.

Der gewählte Ansatz nutzt die Tatsache, dass Vaadin für jeden Benutzer eine eigene VaadinSession verwaltet. Diese Sitzung existiert serverseitig und ist damit weniger anfällig für Manipulationen auf der Client-Seite als etwa ein einfaches Flag im Browser. Indem SessionAuth ein dediziertes Attribut – etwa "authenticated" – in dieser Sitzung setzt, wird der Login-Zustand eindeutig an die aktuelle Browser-Session gekoppelt. Öffnet derselbe Benutzer einen neuen Browser, entsteht eine neue Sitzung, die zunächst wieder als nicht angemeldet gilt.

Im Zusammenspiel der Komponenten ergibt sich daraus ein klarer Fluss: Wenn ein Benutzer auf die Login-Seite gelangt und dort ein korrektes Passwort eingibt, ruft die LoginView nach erfolgreicher Prüfung SessionAuth.markAuthenticated() auf. Anschließend navigiert sie zur administrativen Übersicht. Sobald der Benutzer von dort aus weitere Views öffnet, prüft das MainLayout im Rahmen des Route-Schutzes mit SessionAuth.isAuthenticated(), ob die Sitzung weiterhin als angemeldet gilt. Ist dies der Fall, wird die Navigation zugelassen. Andernfalls wird die Anfrage zur Login-Seite umgelenkt.

Aktiviert der Benutzer schließlich den Logout-Button im Header, sorgt SessionAuth.clearAuthentication() dafür, dass das Authentifizierungsattribut aus der Sitzung entfernt wird und die VaadinSession geschlossen wird. Für den Server ist diese Sitzung damit beendet. Beim nächsten Request wird eine neue Sitzung aufgebaut, die aus Sicht der Anwendung wieder als nicht authentifiziert betrachtet wird, sodass der Route-Schutz die Navigation zurück zur Login-View lenkt.

Dieser Sitzungsansatz passt gut zur Zielsetzung des Projekts: Er ist bewusst einfach gehalten, erfordert keine zusätzliche Infrastruktur und lässt sich leicht nachvollziehen. Gleichzeitig genügt er den Anforderungen eines minimalistischen Admin-Logins, bei dem es weniger um hochgradig abgesicherte, verteilte Authentifizierungsverfahren geht als um eine klare Trennung zwischen „eingeloggt“ und „nicht eingeloggt“ innerhalb einer laufenden Browser-Sitzung.

Im weiteren Verlauf dieses Kapitels gehen wir exemplarisch durch die Implementierung der drei Methoden isAuthenticated, markAuthenticated und clearAuthentication und betrachten, wie sie an den verschiedenen Stellen im Code – Login-View, MainLayout und Logout-Button – zusammenspielen.

Die Implementierung von SessionAuth im Detail

Die Klasse SessionAuth ist bewusst kompakt gehalten. Sie kapselt den Zugriff auf die VaadinSession und stellt drei statische Methoden bereit, die jeweils eine klar umrissene Aufgabe erfüllen:

package com.svenruppert.urlshortener.ui.vaadin.security;

import com.svenruppert.dependencies.core.logger.HasLogger;
import com.vaadin.flow.server.VaadinSession;

import static com.svenruppert.dependencies.core.logger.HasLogger.staticLogger;
import static java.lang.Boolean.TRUE;

public final class SessionAuth
    implements HasLogger {

  private static final String ATTR_AUTH = "authenticated";

  private SessionAuth() {
  }

  public static boolean isAuthenticated() {
    VaadinSession session = VaadinSession.getCurrent();
    if (session == null) return false;
    var attribute = session.getAttribute(ATTR_AUTH);
    staticLogger().info("isAuthenticated.. {}", attribute);
    return TRUE.equals(attribute);
  }

  public static void markAuthenticated() {
    staticLogger().info("markAuthenticated.. ");
    VaadinSession session = VaadinSession.getCurrent();
    if (session != null) {
      session.setAttribute(ATTR_AUTH, TRUE);
    }
  }

  public static void clearAuthentication() {
    staticLogger().info("clearAuthentication.. ");
    VaadinSession session = VaadinSession.getCurrent();
    if (session != null) {
      session.setAttribute(ATTR_AUTH, null);
      session.close();
    }
  }
}

isAuthenticated() – Prüfen, ob eine Sitzung angemeldet ist

Die Methode isAuthenticated() ist die zentrale Leseschnittstelle. Sie wird unter anderem im MainLayout verwendet, um im Rahmen des Route-Schutzes zu entscheiden, ob eine Navigation zugelassen oder zur Login-View umgeleitet werden soll. Intern fragt sie zunächst die aktuelle VaadinSession ab. Existiert keine Sitzung (beispielsweise in sehr frühen Phasen eines Requests), liefert sie sofort false zurück.

Ist eine Sitzung vorhanden, wird das Attribut "authenticated" ausgelesen. Es handelt sich dabei um einen einfachen Objektwert, der beim erfolgreichen Login auf „Boolean.TRUE“ gesetzt wird. Die Methode vergleicht diesen Wert mit TRUE und liefert entsprechend true oder false. Der zusätzliche Log-Eintrag hilft dabei, im laufenden System nachzuvollziehen, wie oft und mit welchem Ergebnis diese Prüfung aufgerufen wird.

markAuthenticated() – Sitzung als angemeldet markieren

Nach einem erfolgreichen Passwort-Check in der LoginView wird markAuthenticated() aufgerufen. Die Methode holt sich die aktuelle VaadinSession und setzt das Attribut "authenticated" auf TRUE. Damit wird der Sitzungszustand so verändert, dass nachfolgende Aufrufe von isAuthenticated() für diese Session positiv ausfallen.

Auch hier sorgt ein Log-Eintrag dafür, dass der Zeitpunkt der Anmeldung nachvollziehbar bleibt. Sollte es im Betrieb zu unerwarteten Zuständen kommen – etwa weil Benutzer melden, „plötzlich wieder ausgeloggt“ zu sein –, lässt sich anhand der Logs besser nachvollziehen, wann Sitzungen markiert oder verworfen wurden.

clearAuthentication() – Abmelden und Sitzung schließen

Die dritte Methode clearAuthentication() ist das Gegenstück zur Anmeldung. Sie wird vom Logout-Button im MainLayout aufgerufen und erfüllt dabei zwei Aufgaben gleichzeitig. Zunächst entfernt sie das Attribut "authenticated" aus der aktuellen Sitzung, indem sie es auf null setzt. Damit gilt isAuthenticated() für diese Session ab diesem Zeitpunkt wieder als false.

Im zweiten Schritt wird „session.close()“ aufgerufen. Dadurch wird die gesamte VaadinSession, einschließlich eventuell gesetzter weiterer Attribute, invalidiert. Diese Maßnahme stellt sicher, dass auch andere sessiongebundene Informationen nicht weitergeleitet werden und ein neuer Request tatsächlich mit einer frischen Sitzung beginnt.

In Kombination mit der expliziten Navigation zurück zur Login-Seite ergibt sich daraus ein sauberer Logout-Fluss: Die Sitzung verliert ihre Berechtigung, der Benutzer verlässt den Admin-Kontext und muss sich für weitere Eingriffe erneut authentifizieren.

Zusammenspiel mit Login-View und MainLayout

Im Gesamtfluss der Anwendung wird SessionAuth an mehreren Stellen verwendet:

  • In der LoginView ruft die Methode attemptLogin() nach erfolgreichem Passwortvergleich SessionAuth.markAuthenticated() auf und navigiert anschließend zur Übersichtsseite.
  • Im MainLayout verwendet die Implementierung von beforeEnter() die Methode SessionAuth.isAuthenticated(), um unautorisierte Zugriffe abzufangen und gegebenenfalls zur Login-View umzuleiten.
  • Der Logout-Button im Header ruft SessionAuth.clearAuthentication() auf und sorgt anschließend durch eine Navigation zur Login-Seite dafür, dass der Benutzer sichtbar aus dem Administrationskontext herausgeführt wird.

Damit konzentriert SessionAuth alle wesentlichen Zugriffe auf den Authentifizierungsstatus an einer Stelle. Änderungen am Sitzungsmodell – etwa ein anderer Attributname oder zusätzliche Metadaten – würden hier vorgenommen und stünden anschließend allen konsumierenden Komponenten konsistent zur Verfügung.

Fazit & Ausblick

Mit der Einführung des einfachen, konfigurierbaren Admin-Logins erhält die Anwendung einen klar definierten Schutzmechanismus für alle administrativen Funktionen, ohne dabei die Komplexität eines vollwertigen Authentifizierungs-Frameworks zu übernehmen. Die Lösung orientiert sich bewusst am Charakter des Projekts: leichtgewichtig, nachvollziehbar und in reiner Core-Java-/Vaadin-Architektur umgesetzt. Gleichzeitig deckt sie die wichtigsten Anforderungen an eine minimale Zugriffskontrolle ab – von der Passwortabfrage über den Route-Schutz bis hin zu einer sauberen Logout-Mechanik.

Das Gesamtsystem besteht aus wenigen, klar voneinander getrennten Bausteinen: einer zentralen Konfigurationsdatei, der LoginConfig zur Auswertung dieser Werte, einer einfachen Login-View für die Benutzerinteraktion, dem Route-Schutz im MainLayout sowie der SessionAuth-Klasse zur Verwaltung des Sitzungszustands. Diese Komponenten greifen wie Zahnräder ineinander und schaffen einen Ablauf, der technisch solide und für Benutzer intuitiv bleibt.

Gleichzeitig ist die Implementierung bewusst offen für Erweiterungen. Der aktuelle Ansatz speichert das Passwort im Klartext in der Konfigurationsdatei und setzt es unverändert als Bytefolge in der Anwendung ein. Für Entwicklungs- und interne Szenarien ist dies völlig ausreichend – allerdings ist klar, dass es nicht für erhöhte Sicherheitsanforderungen geeignet ist. Die Architektur lässt jedoch Raum für folgende Ausbaupunkte:

  • Passwort-Hashing: Die gespeicherten Passwörter könnten auf ein Hash-Verfahren wie SHA-256 oder bcrypt umgestellt werden, sodass keine Klartextwerte auf dem Server vorliegen.
  • Mehrere Benutzer oder Rollen: Die Struktur könnte erweitert werden, um mehrere Administratoren mit individuellen Passwörtern zu unterstützen.
  • Zeitlich begrenzte Sitzungen: Eine automatische Abmeldung bei Inaktivität würde die Sicherheit weiter erhöhen.
  • Two-Factor Authentication (2FA): Für anspruchsvollere Umgebungen könnte eine zweite Sicherheitsstufe – etwa über TOTP – ergänzt werden.
  • Externe Identity Provider: Die Anwendung könnte langfristig an OAuth2/OpenID Connect angebunden werden, sofern dies zum Einsatzszenario passt.

Die Stärke der vorgestellten Lösung liegt jedoch gerade darin, dass sie nicht versucht, diese Aspekte vorwegzunehmen. Sie liefert eine pragmatische, sofort einsatzfähige Grundlage, die den Admin-Bereich zuverlässig vor unbeabsichtigten Zugriffen schützt, ohne das Deployment oder den Entwicklungsprozess unnötig zu erschweren.

Cheers Sven

Total
0
Shares
Previous Post

Adventskalender 2025 – Minimaler Login Prozess – Teil 1

Next Post

Adventskalender 2025 – Aktiv-/Inaktiv-Modell – Teil 1

Related Posts