Passer au contenu principal

Custom Implementation Example

Ouvrir dans ChatGPT

Ce guide vous guide à travers la création d'une implémentation complète de sécurité personnalisée en utilisant l'authentification basée sur les sessions. Vous apprendrez comment les quatre interfaces principales interagissent en les mettant en œuvre à partir de zéro.

La plupart des applications devraient utiliser Spring Security

L'intégration de Spring Security configure automatiquement tout ce qui est montré ici. Ne construisez une sécurité personnalisée que si vous avez des exigences spécifiques ou si vous n'utilisez pas Spring Boot.

Ce que vous allez construire

Un système de sécurité fonctionnel avec quatre classes :

  • SecurityConfiguration - Définit le comportement de sécurité et les emplacements de redirection
  • SecurityContext - Suit qui est connecté en utilisant des sessions HTTP
  • SecurityManager - Coordonne les vérifications de sécurité et fournit la connexion/déconnexion
  • SecurityRegistrar - Connecte tout ensemble au démarrage de l'application

Cet exemple utilise le stockage basé sur les sessions, mais vous pourriez mettre en œuvre les mêmes interfaces en utilisant des requêtes de base de données, LDAP ou tout autre backend d'authentification.

Comment les éléments fonctionnent ensemble

Flux :

  1. SecurityRegistrar s'exécute au démarrage, crée le gestionnaire, enregistre les évaluateurs et attache l'observateur
  2. SecurityManager coordonne tout - il fournit le contexte et la configuration aux évaluateurs
  3. SecurityContext répond à la question "Qui est connecté ?" en lisant depuis les sessions HTTP
  4. SecurityConfiguration répond à la question "Où rediriger ?" pour les pages de connexion et d'accès refusé
  5. Evaluators prennent des décisions d'accès en utilisant le contexte et la configuration

Étape 1 : Définir la configuration de sécurité

La configuration indique au système de sécurité comment se comporter et où rediriger les utilisateurs :

SecurityConfiguration.java
package com.securityplain.security;

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

/**
* Configuration de sécurité pour l'application.
*
* <p>
* Définit où rediriger les utilisateurs lorsque l'authentification est requise ou que l'accès est refusé.
* </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 - La sécurité est active
  • isSecureByDefault() = false - Les routes sont publiques sauf annotation (utilisez true pour exiger l'authentification sur toutes les routes par défaut)
  • /login - Où vont les utilisateurs non authentifiés
  • /access-denied - Où vont les utilisateurs authentifiés sans permissions

Étape 2 : Implémenter le contexte de sécurité

Le contexte suit qui est connecté. Cette implémentation utilise des sessions HTTP pour stocker les informations utilisateur :

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;

/**
* Contexte de sécurité basé sur les sessions.
*
* <p>
* Stocke le principal utilisateur et les rôles dans la session HTTP. Il s'agit d'une implémentation minimale à des fins pédagogiques.
* </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";

Comment ça fonctionne :

  • isAuthenticated() vérifie si un principal utilisateur existe dans la session
  • getPrincipal() récupère le nom d'utilisateur du stockage de session
  • hasRole() vérifie si l'ensemble de rôles de l'utilisateur contient le rôle spécifié
  • getAttribute() / setAttribute() gèrent les attributs de sécurité personnalisés
  • Environment.getSessionAccessor() fournit un accès sécurisé aux sessions

Étape 3 : Créer le gestionnaire de sécurité

Le gestionnaire coordonne les décisions de sécurité. Il étend AbstractRouteSecurityManager, qui gère les chaînes d'évaluateurs et le refus d'accès :

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;

/**
* Implémentation simple du gestionnaire de sécurité.
*
* <p>
* Fournit des méthodes statiques pour la connexion/déconnexion et gère le contexte de sécurité.
* </p>
*/
public class SecurityManager extends AbstractRouteSecurityManager {

Comment ça fonctionne :

  • Étend AbstractRouteSecurityManager pour hériter de la logique de chaîne d'évaluateurs
  • Fournit des implémentations pour getConfiguration() et getSecurityContext()
  • Ajoute login() pour authentifier les utilisateurs et stocker les informations d'identification dans la session
  • Ajoute logout() pour vider la session et rediriger vers la page de connexion
  • Utilise SessionObjectTable pour un stockage simple des sessions
  • Se stocke dans ObjectTable pour un accès global à l'application

Étape 4 : Connecter tout au démarrage

Le registrar connecte tous les éléments lorsque l'application démarre :

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;

/**
* Enregistre les composants de sécurité des routes lors du démarrage de l'application.
*
* <p>
* Configure le gestionnaire de sécurité et les évaluateurs avec le routeur.
* </p>
*/
@AppListenerPriority(1)
public class SecurityRegistrar implements AppLifecycleListener {

/**
* {@inheritDoc}
*/
@Override
public void onWillRun(App app) {
// Créer le gestionnaire de sécurité
SecurityManager securityManager = new SecurityManager();
securityManager.saveCurrent(securityManager);

// Enregistrer les évaluateurs intégrés avec des priorités
securityManager.registerEvaluator(new DenyAllEvaluator(), 0);
securityManager.registerEvaluator(new AnonymousAccessEvaluator(), 1);
securityManager.registerEvaluator(new PermitAllEvaluator(), 2);
securityManager.registerEvaluator(new RolesAllowedEvaluator(), 3);

// Créer l'observateur de sécurité et l'attacher au routeur
RouteSecurityObserver securityObserver = new RouteSecurityObserver(securityManager);
Router router = Router.getCurrent();
if (router != null) {
router.getRenderer().addObserver(securityObserver);
}
}
}

Enregistrer l'écouteur :

Créez src/main/resources/META-INF/services/com.webforj.AppLifecycleListener avec :

com.securityplain.security.SecurityRegistrar

Cela enregistre votre AppLifecycleListener pour qu'il s'exécute au démarrage de l'application.

Comment ça fonctionne :

  • S'exécute tôt (@AppListenerPriority(1)) pour configurer la sécurité avant le chargement des routes
  • Crée le gestionnaire de sécurité et le stocke globalement
  • Enregistre les évaluateurs intégrés dans l'ordre de priorité (les nombres inférieurs s'exécutent en premier)
  • Crée l'observateur qui intercepte la navigation
  • Attache l'observateur au routeur afin que les vérifications de sécurité se produisent automatiquement

Après cela, la sécurité est active pour toute navigation.

Utiliser votre implémentation

Créer une vue de connexion

La vue suivante utilise le composant Login.

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("Connexion")
@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());
}
}