Spring Security con JSF PrimeFaces y Spring Boot mediante JoinFaces
Integrar Spring Boot con JSF (Jakarta Server Faces) puede ser una excelente opción para desarrolladores que buscan combinar lo mejor de ambos marcos de trabajo, sobre todo la integración de estas tecnologías para desarrollar aplicaciones web robustas y seguras. Una combinación poderosa es el uso de Spring Security, JSF con PrimeFaces y Spring Boot, todo facilitado mediante el framework JoinFaces.
Antes, configurar tecnologías como Spring Security, JSF y PrimeFaces requería el uso de múltiples archivos XML extensos y detallados, como web.xml
y faces-config.xml
. Estas configuraciones eran verbosas, propensas a errores, y difíciles de mantener. Cada nueva funcionalidad implicaba agregar configuraciones en varios archivos, lo que dispersaba las definiciones y hacía complejo entender el flujo de la aplicación.
Además, integrar Spring y JSF era desafiante porque ambos frameworks eran independientes y no compartían contexto de forma nativa. Esto exigía configuraciones manuales para habilitar la comunicación entre los beans de Spring y el contexto de JSF. La falta de validación automática en XML hacía que los errores fueran difíciles de detectar hasta el momento de la ejecución, lo que ralentizaba el desarrollo.
Con la llegada de herramientas como JoinFaces y las configuraciones basadas en anotaciones, este proceso se simplificó drásticamente. Ahora, toda la configuración puede centralizarse en clases Java, con menos código y mayor claridad. Las anotaciones permiten un desarrollo más ágil, con validaciones automáticas y soporte de IDE modernos, eliminando la necesidad de archivos XML complejos y reduciendo significativamente el esfuerzo de integración y mantenimiento.
¿Es correcto usar Spring Security y JSF?
Esta configuración es altamente recomendable para aplicaciones empresariales donde se necesitan interfaces modernas, seguridad robusta y un mantenimiento efectivo. Sin embargo, para proyectos más pequeños o simples, una solución más ligera podría ser suficiente.
Ventajas
- Integración simplificada: JoinFaces reduce la configuración manual.
- Compatibilidad visual y de seguridad: PrimeFaces y Spring Security se complementan para crear aplicaciones seguras y modernas.
- Mantenimiento: Spring Boot ofrece facilidad en actualizaciones y escalabilidad.
Desventajas
- Curva de aprendizaje: Requiere conocimientos previos tanto de JSF como de Spring Boot.
- Compatibilidad limitada: Algunas bibliotecas de terceros pueden requerir ajustes manuales.
- Complejidad inicial: La configuración puede ser laboriosa para proyectos pequeños.
Beneficios
- Seguridad robusta: Gracias a Spring Security, se obtiene una protección avanzada.
- Interfaz moderna: PrimeFaces proporciona componentes atractivos y funcionales.
- Productividad: La integración con Spring Boot permite un desarrollo ágil.
Este enfoque garantiza una base sólida para construir aplicaciones web seguras, escalables y visualmente atractivas. ¡Experimenta con esta combinación y lleva tus proyectos al siguiente nivel!
1. Crear Proyecto Spring Boot
La forma más sencilla de comenzar a utilizar Spring Boot es a través del inicializador Spring Boot que está disponible en https://start.spring.io/
- Asegúrate de utilizar Spring Boot en su versión 3 y seleccionar las dependencias que necesitas.
- Considera siempre la versión de Java a utilizar, como vemos en este caso el JDK 17 es la versión compatible desde ahora dejando a un lado la versión del JDK 8 y JDK 11.
A continuación, descargue el proyecto y descomprima en su ordenador con WinRAR. En la siguiente sección, le mostraremos cómo adaptar esta aplicación Spring Boot y Spring Security para que se ejecute con JSF, PrimeFaces usando JoinFaces.
2. Configurar el POM
A continuación, necesitamos aplicar algunos cambios en nuestro pom.xml.
Al integrar Spring Boot con JSF, es importante considerar las dependencias necesarias que puede encontrarlas en el sitio oficial de GitHub de JoinFaces.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.joinfaces</groupId>
<artifactId>joinfaces-platform</artifactId>
<version>5.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- PrimeFaces -->
<!-- Ya incluye las librerias de JSF por defecto -->
<dependency>
<groupId>org.joinfaces</groupId>
<artifactId>primefaces-spring-boot-starter</artifactId>
</dependency>
<!-- Spring Security con JoinFaces -->
<dependency>
<groupId>org.joinfaces</groupId>
<artifactId>security-spring-boot-starter</artifactId>
</dependency>
<!-- otras dependencias -->
</dependencies>
3. Configurar algunas propiedades de la aplicación
Para que Spring Boot pueda ser desplegado con JSF, necesitas hacer unas configuraciones en el application.properties, pero en este caso usaremos application.yml.
# Tema (por default es el tema de saga, tema oscuro es vela)
joinfaces:
primefaces:
theme: saga
# Conexion a base de datos mysql (en caso de usuarse)
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_springboot_security_jsf?createDatabaseIfNotExist=true
username: root
password:
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
database-platform: org.hibernate.dialect.MySQL8Dialect
4. Estructura del Proyecto
Cuando trabajas con JoinFaces en una aplicación web, la estructura de paquetes y archivos debe estar bien organizada para aprovechar tanto las convenciones de Spring Boot como las características de JSF. Aquí te presento una estructura sugerida para un proyecto JoinFaces:
src/
└── main/
├── java/
│ └── com.example.myapp/ <-- Paquete base
│ ├── config/ <-- Configuración del proyecto
│ │ └── security/
│ │ └── SecurityConfig.java
│ │ └── AppConfig.java
│ ├── controller/ <-- Managed beans / controladores JSF
│ │ └── UserController.java
│ ├── exception/ <-- Excepciones personalizadas
│ │ └── CustomException.java
│ ├── handler/ <-- Excepciones de forma centralizada.
│ │ └── GlobalExceptionHandler.java
│ ├── model/ <-- Entidades JPA
│ │ └── User.java
│ ├── repository/ <-- Repositorios JPA
│ │ └── UserRepository.java
│ ├── service/ <-- Servicios (lógica de negocio)
│ │ └── UserService.java
│ └── Application.java <-- Clase principal de Spring Boot
└── resources/
├── META-INF/
│ └── resources/ <-- Archivos estáticos y páginas JSF
│ ├── templates/ <-- Plantillas comunes (ej. layouts.xhtml)
│ ├── views/ <-- Vistas JSF (ej. login.xhtml, home.xhtml)
│ └── resources/
│ ├── css/ <-- Archivos CSS personalizados
│ ├── js/ <-- Archivos JS personalizados
│ └── images/ <-- Imágenes
├── application.properties <-- Configuración de Spring Boot
└── logback-spring.xml <-- Configuración de logs (opcional)
Autorización y autenticación de seguridad JSF
Spring Security viene con un generador de página de inicio de sesión predeterminado, pero en este ejemplo configuraremos una página de inicio de sesión personalizada utilizando componentes PrimeFaces, pero para facilitar el desarrollo te dejo unos diseños que realice desde mi repositorio de GitHub, encontraras recursos css y las vistas correspondientes para el login y el home.
Al configurar Spring Security con JSF, es importante que los campos de entrada del formulario de autenticación tengan los atributos id
configurados como username
y password
. Esto se debe a que, de manera predeterminada, Spring Security espera recibir estos nombres de parámetros para procesar la autenticación correctamente.
Además, es necesario incluir el atributo prependId="false"
en el formulario JSF. Esto evita que el marco JSF anteponga el identificador del formulario (formId
) a los parámetros, lo cual modificaría sus nombres (por ejemplo, formId:username
en lugar de username
) y provocaría que Spring Security no pueda reconocerlos durante el proceso de validación.
Esta configuración asegura que el formulario sea compatible con las expectativas de Spring Security, facilitando una integración fluida entre ambas tecnologías.
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:p="primefaces">
<h:head>
<title>Login</title>
<meta content='text/html; charset=UTF-8' http-equiv="Content-Type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="mobile-web-app-capable" content="yes"/>
<h:outputStylesheet library="assets" name="css/primeflex.css"/>
<h:outputStylesheet library="assets" name="css/primeicons.css"/>
<h:outputStylesheet library="assets" name="css/stylle.css"/>
</h:head>
<h:body>
<div class="px-4 py-8 md:px-6 lg:px-8">
<div class="flex align-items-center justify-content-center">
<div class="surface-card p-4 shadow-2 border-round w-full lg:w-3 md:w-7">
<div class="text-center mb-5">
<div class="text-900 text-3xl font-medium mb-3">Bienvenido</div>
</div>
<div>
<h:form prependId="false">
<label for="username" class="block text-900 font-medium mb-2">Usuario</label>
<p:inputText id="username" styleClass="w-full mb-3" placeholder="Usuario"/>
<label for="password" class="block text-900 font-medium mb-2">Clave</label>
<p:password id="password" styleClass="w-full mb-3" placeholder="Clave"/>
<p:commandButton id="submit" value="Login" ajax="false"
icon="pi pi-user" styleClass="w-full"/>
</h:form>
</div>
</div>
</div>
</div>
</h:body>
</html>
Ahora implementaremos la capa de seguridad con Spring Security para que pueda conectarse al formulario. Personalizaremos la configuración de seguridad de la aplicación web mediante la creación de una clase llamada SecurityConfig
.
import com.example.demo.config.properties.ApplicationUsers;
import com.example.demo.config.properties.UserCredentials;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
//metodo auxiliar para ccrear coincidencias de rutas con MVC
@Bean
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
return new MvcRequestMatcher.Builder(introspector);
}
//crea los usuarios en memoria
@Bean
public InMemoryUserDetailsManager userDetailsService(ApplicationUsers applicationUsers) {
// Crea un codificador de contraseñas
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
// Crea un administrador de detalles de usuario en memoria
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// Crea usuarios manualmente
manager.createUser(User.builder()
.username("admin")
.password(encoder.encode("123")) // Contraseña codificada
.authorities("ROLE_ADMIN") // Rol de admin
.build());
manager.createUser(User.builder()
.username("user")
.password(encoder.encode("123")) // Contraseña codificada
.authorities("ROLE_USER") // Rol de usuario
.build());
// Devuelve el administrador de detalles de usuario con los usuarios definidos
return manager;
}
@Bean
public SecurityFilterChain configure(HttpSecurity http, MvcRequestMatcher.Builder mvc) {
try {
http.csrf(AbstractHttpConfigurer::disable);
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(mvc.pattern("/login.xhtml")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/jakarta.faces.resource/**")).permitAll()
.anyRequest()
.authenticated()
)
.formLogin(formLogin -> formLogin
.loginPage("/login.xhtml").permitAll()
.failureUrl("/login.xhtml?error=true")
.defaultSuccessUrl("/home.xhtml")
)
.logout(logout -> logout
.logoutSuccessUrl("/login.xhtml")
.deleteCookies("JSESSIONID")
);
return http.build();
} catch (Exception ex) {
throw new BeanCreationException("Wrong spring security configuration", ex);
}
}
}
En este fragmento de código, está configurando la seguridad de una aplicación Spring Boot utilizando Spring Security, lo que incluye la autenticación y autorización de usuarios. A continuación, te explico qué está haciendo cada parte del código, paso a paso.
- @Configuration: Esta anotación indica que la clase es una clase de configuración, lo que significa que define varios componentes y beans de Spring.
- @EnableWebSecurity: Habilita la seguridad web en tu aplicación, lo que significa que configurará la protección contra accesos no autorizados a las rutas.
Se crea el metodo MvcRequestMatcher para crear un mecanismo de rutas seguras que soporta patrones más avanzados (como los definidos en controladores MVC). Esto mejora la flexibilidad al configurar rutas.
//metodo auxiliar para ccrear coincidencias de rutas con MVC
@Bean
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
return new MvcRequestMatcher.Builder(introspector);
}
Para este ejemplo utilizamos autenticación en memoria en la que se definen dos usuarios ( admin
y user
) con diferentes roles ROLE_USER
y ROLE_ADMIN
.
//crea los usuarios en memoria
@Bean
public InMemoryUserDetailsManager userDetailsService(ApplicationUsers applicationUsers) {
// Crea un codificador de contraseñas
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
// Crea un administrador de detalles de usuario en memoria
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// Crea usuarios manualmente
manager.createUser(User.builder()
.username("admin")
.password(encoder.encode("123")) // Contraseña codificada
.authorities("ROLE_ADMIN") // Rol de admin
.build());
manager.createUser(User.builder()
.username("user")
.password(encoder.encode("123")) // Contraseña codificada
.authorities("ROLE_USER") // Rol de usuario
.build());
// Devuelve el administrador de detalles de usuario con los usuarios definidos
return manager;
}
PasswordEncoder: Utiliza el codificador delegado (
PasswordEncoderFactories.createDelegatingPasswordEncoder()
) para encriptar las contraseñas. Este codificador es flexible y soporta múltiples algoritmos de encriptación.InMemoryUserDetailsManager: Este es un servicio que guarda los detalles de los usuarios en memoria (sin necesidad de una base de datos). Se utiliza para la gestión de usuarios en aplicaciones pequeñas o de prueba.
Creación de usuarios: En lugar de leer usuarios desde un archivo de propiedades, creamos usuarios directamente en el código utilizando
manager.createUser()
. Cada usuario tiene un nombre de usuario (username
), una contraseña codificada (password
) y un conjunto de roles o autoridades (authorities
).
Por ultimo creamos el método para definir cuándo y cómo deben autenticarse los usuarios, donde se define las reglas de acceso para las URLs de la aplicación y personaliza el flujo de inicio de sesión, cierre de sesión, y manejo de errores.
@Bean
public SecurityFilterChain configure(HttpSecurity http, MvcRequestMatcher.Builder mvc) {
try {
http.csrf(AbstractHttpConfigurer::disable);
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(mvc.pattern("/login.xhtml")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/jakarta.faces.resource/**")).permitAll()
.anyRequest()
.authenticated()
)
.formLogin(formLogin -> formLogin
.loginPage("/login.xhtml").permitAll()
.failureUrl("/login.xhtml?error=true")
.defaultSuccessUrl("/home.xhtml")
)
.logout(logout -> logout
.logoutSuccessUrl("/login.xhtml")
.deleteCookies("JSESSIONID")
);
return http.build();
} catch (Exception ex) {
throw new BeanCreationException("Wrong spring security configuration", ex);
}
}
Aquí se esta creando un bean que configura la seguridad de la aplicación. HttpSecurity
es una clase que permite configurar cómo se protegen las rutas y se maneja la autenticación. Lo que sigue es la configuración específica.
- CSRF (Cross-Site Request Forgery): Es una protección que impide que un usuario malintencionado realice acciones en nombre de otro usuario. En este caso, lo deshabilitamos (lo cual se hace generalmente si la aplicación solo tiene interacción a través de formularios y no usa una API RESTful).
- http.authorizeHttpRequests: Es una configuración de seguridad que se encarga de definir las reglas de acceso a las distintas URL de la aplicación. La configuración se aplica a las peticiones HTTP que llegan a la aplicación. Aquí se define qué usuarios pueden acceder a qué rutas de la aplicación:
- permitAll(): Las rutas
/login.xhtml
y/jakarta.faces.resource/**
son públicas, es decir, cualquier usuario puede acceder sin necesidad de estar autenticado. - hasAuthority(«ROLE_X»): Las rutas
/admin/**
solo las pueden acceder usuarios con el rolROLE_ADMIN
, y/user/**
solo los usuarios con el rolROLE_USER
. - hasAnyAuthority(«ROLE_X», «ROLE_Y»): La ruta
/nosotros.xhtml
puede ser accedida por usuarios con cualquiera de los rolesROLE_USER
oROLE_ADMIN
. - anyRequest().authenticated(): Todas las demás rutas requieren que el usuario esté autenticado.
- loginPage(«/login.xhtml»): Se configura la página de inicio de sesión personalizada (en lugar de la página predeterminada de Spring Security).
- failureUrl(«/login.xhtml?error=true»): Si el inicio de sesión falla, el usuario será redirigido a la misma página de login con un parámetro de error.
- defaultSuccessUrl(«/home.xhtml»): Si el inicio de sesión es exitoso, el usuario será redirigido a la página de inicio (
/home.xhtml
). - logout(): Aquí configuras cómo manejar el cierre de sesión. El usuario será redirigido a la página de login después de cerrar sesión, y se eliminarán las cookies asociadas a la sesión (
JSESSIONID
).
- permitAll(): Las rutas
Conclusión
Spring Security es una poderosa herramienta que te permite proteger tu aplicación web JSF con facilidad. Solo necesitas hacer una configuración básica para gestionar la autenticación (verificar quién es el usuario) y la autorización (controlar qué rutas puede acceder según sus permisos). Con esta configuración, puedes asegurarte de que solo los usuarios adecuados tengan acceso a las secciones de tu aplicación, como las creadas con PrimeFaces.
Usar Spring Security con JoinFaces es una elección sólida y segura para aplicaciones JSF en Spring Boot. Permite integrar la seguridad de manera oficial y sencilla, protegiendo tanto la autenticación como la autorización en tus páginas JSF y asegurando la protección de tu aplicación frente a posibles amenazas.
Tutorial de Configuración con y sin base de datos
WALTER ROSERO
Creador de Walter Rosero - SuperTecnoDroid | Responsable del Canal de YouTube Walter Rosero. Amante de la tecnología y juegos, me gusta aportar con un granito de arena al aprendizaje y la enseñanza. Soy editor y compositor en el área multimedia. Combino mi pasión por la enseñanza a través de la World Wide Web.