Saltar al contenido principal

Custom Implementation Example

Abrir en ChatGPT

Esta guía explica cómo construir una implementación de seguridad personalizada completa utilizando autenticación basada en sesiones. Aprenderás cómo las cuatro interfaces principales trabajan juntas implementándolas desde cero.

La mayoría de las aplicaciones deben usar Spring Security

La integración de Spring Security configura automáticamente todo lo que se muestra aquí. Solo construye seguridad personalizada si tienes requisitos específicos o no estás utilizando Spring Boot.

Lo que construirás

Un sistema de seguridad funcional con cuatro clases:

  • SecurityConfiguration - Define el comportamiento de seguridad y las ubicaciones de redirección
  • SecurityContext - Rastrea quién ha iniciado sesión utilizando sesiones HTTP
  • SecurityManager - Coordina las verificaciones de seguridad y proporciona inicio/cierre de sesión
  • SecurityRegistrar - Conecta todo al inicio de la aplicación

Este ejemplo utiliza almacenamiento basado en sesiones, pero podrías implementar las mismas interfaces utilizando consultas a bases de datos, LDAP, o cualquier otro backend de autenticación.

Cómo funcionan juntas las piezas

Flujo:

  1. SecurityRegistrar se ejecuta al inicio, crea el administrador, registra los evaluadores y adjunta el observador
  2. SecurityManager coordina todo - proporciona el contexto y la configuración a los evaluadores
  3. SecurityContext responde "¿Quién ha iniciado sesión?" leyendo de las sesiones HTTP
  4. SecurityConfiguration responde "¿Dónde redirigir?" para las páginas de inicio de sesión y acceso denegado
  5. Evaluators toman decisiones de acceso utilizando el contexto y la configuración

Paso 1: Definir la configuración de seguridad

La configuración le dice al sistema de seguridad cómo comportarse y dónde redirigir a los usuarios:

SecurityConfiguration.java
package com.securityplain.security;

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

/**
* Configuración de seguridad para la aplicación.
*
* <p>
* Define dónde redirigir a los usuarios cuando se requiere autenticación o el acceso es denegado.
* </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 seguridad está activa
  • isSecureByDefault() = false - Las rutas son públicas a menos que estén anotadas (usa true para requerir autenticación en todas las rutas por defecto)
  • /login - Donde van los usuarios no autenticados
  • /access-denied - Donde van los usuarios autenticados sin permisos

Paso 2: Implementar el contexto de seguridad

El contexto rastrea quién ha iniciado sesión. Esta implementación utiliza sesiones HTTP para almacenar la información del usuario:

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;

/**
* Contexto de seguridad simple basado en sesiones.
*
* <p>
* Almacena el principal del usuario y los roles en la sesión HTTP. Esta es una implementación mínima para fines educativos.
* </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";

Cómo funciona:

  • isAuthenticated() verifica si existe un principal de usuario en la sesión
  • getPrincipal() recupera el nombre de usuario del almacenamiento de sesión
  • hasRole() verifica si el conjunto de roles del usuario contiene el rol especificado
  • getAttribute() / setAttribute() gestionan atributos de seguridad personalizados
  • Environment.getSessionAccessor() proporciona acceso a sesiones de manera segura para hilos

Paso 3: Crear el administrador de seguridad

El administrador coordina las decisiones de seguridad. Extiende AbstractRouteSecurityManager, que maneja cadenas de evaluadores y la denegación de acceso:

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;

/**
* Implementación simple del administrador de seguridad.
*
* <p>
* Proporciona métodos estáticos para inicio de sesión/cierre de sesión y gestiona el contexto de seguridad.
* </p>
*/
public class SecurityManager extends AbstractRouteSecurityManager {

Cómo funciona:

  • Extiende AbstractRouteSecurityManager para heredar la lógica de la cadena de evaluadores
  • Proporciona las implementaciones de getConfiguration() y getSecurityContext()
  • Agrega login() para autenticar a los usuarios y almacenar credenciales en la sesión
  • Agrega logout() para limpiar la sesión y redirigir a la página de inicio de sesión
  • Utiliza SessionObjectTable para un almacenamiento sencillo de sesiones
  • Se almacena en ObjectTable para acceso en toda la aplicación

Paso 4: Conectar todo al inicio

El registrador conecta todas las piezas cuando la aplicación se inicia:

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;

/**
* Registra componentes de seguridad de ruta durante el inicio de la aplicación.
*
* <p>
* Configura el administrador de seguridad y los evaluadores con el enrutador.
* </p>
*/
@AppListenerPriority(1)
public class SecurityRegistrar implements AppLifecycleListener {

/**
* {@inheritDoc}
*/
@Override
public void onWillRun(App app) {
// Crear administrador de seguridad
SecurityManager securityManager = new SecurityManager();
securityManager.saveCurrent(securityManager);

// Registrar evaluadores incorporados con prioridades
securityManager.registerEvaluator(new DenyAllEvaluator(), 0);
securityManager.registerEvaluator(new AnonymousAccessEvaluator(), 1);
securityManager.registerEvaluator(new PermitAllEvaluator(), 2);
securityManager.registerEvaluator(new RolesAllowedEvaluator(), 3);

// Crear observador de seguridad y adjuntarlo al enrutador
RouteSecurityObserver securityObserver = new RouteSecurityObserver(securityManager);
Router router = Router.getCurrent();
if (router != null) {
router.getRenderer().addObserver(securityObserver);
}
}
}

Registrar el oyente:

Crea src/main/resources/META-INF/services/com.webforj.AppLifecycleListener con:

com.securityplain.security.SecurityRegistrar

Esto registra tu AppLifecycleListener para que se ejecute al inicio de la aplicación.

Cómo funciona:

  • Se ejecuta temprano (@AppListenerPriority(1)) para configurar la seguridad antes de que se carguen las rutas
  • Crea el administrador de seguridad y lo almacena globalmente
  • Registra evaluadores incorporados en orden de prioridad (los números más bajos se ejecutan primero)
  • Crea el observador que intercepta la navegación
  • Adjunta el observador al enrutador para que las verificaciones de seguridad ocurran automáticamente

Después de que esto se ejecute, la seguridad está activa para toda la navegación.

Usando tu implementación

Crear una vista de inicio de sesión

La siguiente vista usa el componente 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("Iniciar Sesión")
@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());
}
}