Zum Hauptinhalt springen

Custom Implementation Example

In ChatGPT öffnen

Dieser Leitfaden führt durch den Aufbau einer vollständigen benutzerdefinierten Sicherheitsimplementierung mit sitzungsbasierter Authentifizierung. Sie lernen, wie die vier Kernschnittstellen zusammenarbeiten, indem Sie sie von Grund auf neu implementieren.

Die meisten Apps sollten Spring Security verwenden

Die Spring Security-Integration konfiguriert automatisch alles, was hier gezeigt wird. Erstellen Sie nur benutzerdefinierte Sicherheit, wenn Sie spezifische Anforderungen haben oder Spring Boot nicht verwenden.

Was Sie erstellen werden

Ein funktionierendes Sicherheitssystem mit vier Klassen:

  • SecurityConfiguration - Definiert das Sicherheitsverhalten und die Weiterleitungsstandorte
  • SecurityContext - Verfolgt, wer mit HTTP-Sitzungen angemeldet ist
  • SecurityManager - Koordiniert Sicherheitsüberprüfungen und bietet Anmeldungen/Abmeldungen
  • SecurityRegistrar - Verkabelt alles zusammen beim Start der App

Dieses Beispiel verwendet sitzungsbasierte Speicherung, aber Sie könnten die gleichen Schnittstellen mit Datenbankabfragen, LDAP oder einem anderen Authentifizierungs-Backend implementieren.

Wie die Komponenten zusammenarbeiten

Ablauf:

  1. SecurityRegistrar läuft beim Start, erstellt den Manager, registriert Evaluatoren und hängt den Beobachter an
  2. SecurityManager koordiniert alles - er stellt den Kontext und die Konfiguration für die Evaluatoren bereit
  3. SecurityContext beantwortet die Frage "Wer ist angemeldet?" durch Lesen von HTTP-Sitzungen
  4. SecurityConfiguration beantwortet die Frage "Wohin umleiten?" für Anmelde- und Zugriffsverweigerungsseiten
  5. Evaluators treffen Zugriffsentscheidungen mithilfe des Kontextes und der Konfiguration

Schritt 1: Sicherheitskonfiguration definieren

Die Konfiguration sagt dem Sicherheitssystem, wie es sich verhalten soll und wohin es Benutzer umleiten soll:

SecurityConfiguration.java
package com.securityplain.security;

import com.webforj.router.history.Location;
import com.webforj.router.security.RouteSecurityConfiguration;
import java.util.Optional;

/**
* Sicherheitskonfiguration für die Anwendung.
*
* <p>
* Definiert, wohin Benutzer umgeleitet werden, wenn eine Authentifizierung erforderlich ist oder der Zugriff verweigert wird.
* </p>
*/
public class SecurityConfiguration implements RouteSecurityConfiguration {

@Override
public boolean isEnabled() {
return true;
}

@Override
public boolean isSecureByDefault() {
return false;
}

@Override
public Optional<Location> getAuthenticationLocation() {
return Optional.of(new Location("/login"));
}

@Override
public Optional<Location> getDenyLocation() {
return Optional.of(new Location("/access-denied"));
}
}
  • isEnabled() = true - Sicherheit ist aktiv
  • isSecureByDefault() = false - Routen sind öffentlich, es sei denn, sie sind annotiert (verwenden Sie true, um die Authentifizierung für alle Routen standardmäßig zu verlangen)
  • /login - Wohin nicht authentifizierte Benutzer gehen
  • /access-denied - Wohin authentifizierte Benutzer ohne Berechtigungen gehen

Schritt 2: Sicherheitskontext implementieren

Der Kontext verfolgt, wer angemeldet ist. Diese Implementierung verwendet HTTP-Sitzungen, um Benutzerinformationen zu speichern:

SecurityContext.java
package com.securityplain.security;

import com.webforj.Environment;
import com.webforj.router.security.RouteSecurityContext;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
* Einfacher sitzungsbasierter Sicherheitskontext.
*
* <p>
* Speichert Benutzerprinzipal und Rollen in HTTP-Sitzungen. Dies ist eine minimale Implementierung zu Lehrzwecken.
* </p>
*/
public class SecurityContext implements RouteSecurityContext {
private static final String SESSION_USER_KEY = "security.user";
private static final String SESSION_ROLES_KEY = "security.roles";
private static final String SESSION_ATTRS_KEY = "security.attributes";

Wie es funktioniert:

  • isAuthenticated() überprüft, ob ein Benutzerprinzipal in der Sitzung vorhanden ist
  • getPrincipal() ruft den Benutzernamen aus der Sitzungsspeicherung ab
  • hasRole() prüft, ob die Rollensammlung des Benutzers die angegebene Rolle enthält
  • getAttribute() / setAttribute() verwalten benutzerdefinierte Sicherheitsattribute
  • Environment.getSessionAccessor() bietet thread-sicheren Zugriff auf die Sitzung

Schritt 3: Sicherheitsmanager erstellen

Der Manager koordiniert Sicherheitsentscheidungen. Er erweitert AbstractRouteSecurityManager, das die Evaluatorenketten und Zugriffsverweigerungen behandelt:

SecurityManager.java
package com.securityplain.security;

import com.webforj.environment.ObjectTable;
import com.webforj.environment.SessionObjectTable;
import com.webforj.router.Router;
import com.webforj.router.security.AbstractRouteSecurityManager;
import com.webforj.router.security.RouteAccessDecision;
import com.webforj.router.security.RouteSecurityConfiguration;
import com.webforj.router.security.RouteSecurityContext;

import java.util.Set;

/**
* Einfache Implementierung des Sicherheitsmanagers.
*
* <p>
* Bietet statische Methoden für Anmeldung/Abmeldung und verwaltet den Sicherheitskontext.
* </p>
*/
public class SecurityManager extends AbstractRouteSecurityManager {

Wie es funktioniert:

  • Erweitert AbstractRouteSecurityManager, um die Logik der Evaluatorenketten zu erben
  • Bietet Implementierungen für getConfiguration() und getSecurityContext()
  • Fügt login() hinzu, um Benutzer zu authentifizieren und Anmeldeinformationen in der Sitzung zu speichern
  • Fügt logout() hinzu, um die Sitzung zu leeren und zur Anmeldeseite weiterzuleiten
  • Verwendet SessionObjectTable für einfachen Sitzungspeicher
  • Speichert sich selbst in ObjectTable für den zugriff auf die gesamte App

Schritt 4: Alles beim Start verbinden

Der Registrar verbindet alle Teile, wenn die App startet:

SecurityRegistrar.java
package com.securityplain.security;

import com.webforj.App;
import com.webforj.AppLifecycleListener;
import com.webforj.annotation.AppListenerPriority;
import com.webforj.router.Router;
import com.webforj.router.security.RouteSecurityObserver;
import com.webforj.router.security.evaluator.AnonymousAccessEvaluator;
import com.webforj.router.security.evaluator.DenyAllEvaluator;
import com.webforj.router.security.evaluator.PermitAllEvaluator;
import com.webforj.router.security.evaluator.RolesAllowedEvaluator;

/**
* Registriert Routensicherheitselemente während des Anwendungsstarts.
*
* <p>
* Richten Sie den Sicherheitsmanager und Evaluatoren mit dem Router ein.
* </p>
*/
@AppListenerPriority(1)
public class SecurityRegistrar implements AppLifecycleListener {

/**
* {@inheritDoc}
*/
@Override
public void onWillRun(App app) {
// Sicherheitsmanager erstellen
SecurityManager securityManager = new SecurityManager();
securityManager.saveCurrent(securityManager);

// Eingebaute Evaluatoren mit Prioritäten registrieren
securityManager.registerEvaluator(new DenyAllEvaluator(), 0);
securityManager.registerEvaluator(new AnonymousAccessEvaluator(), 1);
securityManager.registerEvaluator(new PermitAllEvaluator(), 2);
securityManager.registerEvaluator(new RolesAllowedEvaluator(), 3);

// Sicherheitsbeobachter erstellen und an Router anhängen
RouteSecurityObserver securityObserver = new RouteSecurityObserver(securityManager);
Router router = Router.getCurrent();
if (router != null) {
router.getRenderer().addObserver(securityObserver);
}
}
}

Registrieren Sie den Listener:

Erstellen Sie src/main/resources/META-INF/services/com.webforj.AppLifecycleListener mit:

com.securityplain.security.SecurityRegistrar

Dies registriert Ihren AppLifecycleListener, damit er beim App-Start läuft.

Wie es funktioniert:

  • Läuft früh (@AppListenerPriority(1)), um die Sicherheit einzurichten, bevor die Routen geladen werden
  • Erstellt den Sicherheitsmanager und speichert ihn global
  • Registriert eingebaute Evaluatoren in der Prioritätsreihenfolge (niedrigere Zahlen werden zuerst ausgeführt)
  • Erstellt den Beobachter, der die Navigation abfängt
  • Fügt den Beobachter dem Router hinzu, damit Sicherheitsüberprüfungen automatisch erfolgen

Nachdem dies ausgeführt wurde, ist die Sicherheit bei allen Navigationen aktiv.

Verwendung Ihrer Implementierung

Erstellen Sie eine Anmeldeseite

Die folgende Ansicht verwendet die Login Komponente.

LoginView.java
package com.securityplain.views;

import com.securityplain.security.SecurityManager;
import com.webforj.component.Composite;
import com.webforj.component.login.Login;
import com.webforj.router.Router;
import com.webforj.router.annotation.FrameTitle;
import com.webforj.router.annotation.Route;
import com.webforj.router.history.Location;
import com.webforj.router.security.annotation.AnonymousAccess;

@Route("/login")
@FrameTitle("Anmelden")
@AnonymousAccess
public class LoginView extends Composite<Login> {
private final Login self = getBoundComponent();

public LoginView() {
self.onSubmit(e -> {
var result = SecurityManager.getCurrent().login(
e.getUsername(), e.getPassword()
);

if (result.isGranted()) {
Router.getCurrent().navigate(new Location("/"));
} else {
self.setError(true);
self.setEnabled(true);
}
});

self.whenAttached().thenAccept(c -> self.open());
}
}