Ahora si vas a entender Spring Security
Introducción.
¿Qué es Spring Security?
Importancia de la seguridad en aplicaciones web modernas
Objetivo del artículo
Fundamentos de Seguridad en Aplicaciones Web
Conceptos básicos: autenticación vs autorización
-
Autenticación (Authentication): Es el proceso de verificar la identidad de un usuario. Por ejemplo, cuando alguien ingresa su nombre de usuario y contraseña, el sistema valida que esa persona es quien dice ser.
-
Autorización (Authorization): Determina qué acciones puede realizar un usuario autenticado. Por ejemplo, un usuario puede ver su perfil, pero solo un administrador puede eliminar otros usuarios.
Modelos comunes de seguridad
En las aplicaciones web, los modelos de seguridad más comunes incluyen:
-
Roles: Etiquetas que agrupan permisos. Ejemplo: USER, ADMIN.
-
Permisos o authorities: Acciones específicas que un rol puede realizar, como READ_BOOK o DELETE_ORDER.
-
Matriz de control de acceso (ACL): Define qué roles o usuarios tienen acceso a cada recurso o acción.
Spring Security permite trabajar tanto con roles como con permisos detallados, lo que facilita implementar políticas de acceso claras y mantenibles.
Arquitectura General de Spring Security
Breve historia y evolución
Principios de diseño
Spring Security se basa en tres principios clave:
-
Modularidad: Cada componente tiene una función específica y puede ser reemplazado o extendido sin afectar el resto del sistema.
-
Extensibilidad: Permite personalizar mecanismos de autenticación, autorización y manejo de errores según las necesidades de la aplicación.
-
Integración: Se integra de forma nativa con Spring y sus proyectos relacionados, facilitando la configuración y la coherencia en toda la aplicación.
Componentes principales de Spring Security
Filter Chain (Filtros de seguridad):
AuthenticationManager:
UserDetailsService:
SecurityContextHolder:
AccessDecisionManager:
IMPORTANTE: Estos componentes no funcionan de forma aislada: cada solicitud atraviesa la cadena de filtros, se autentica con el AuthenticationManager, se consulta el UserDetailsService para obtener información del usuario, se guarda el contexto en SecurityContextHolder y finalmente el AccessDecisionManager determina si el acceso es permitido. En la próxima sección veremos en detalle cómo ocurre esta interacción paso a paso.
Proxy Chains en la Cadena de Servlets
¿Qué es un Servlet y un Servlet Filter?
Los filtros interceptan solicitudes HTTP antes de que lleguen a un Servlet y se organizan en una cadena, de manera que cada solicitud pasa por todos los filtros registrados antes de alcanzar el recurso final. Esto permite realizar tareas como:
-
Autenticación y autorización
-
Registro de actividad (logging)
-
Transformación de datos de la solicitud o respuesta
-
Manejo de excepciones
Introducción a las proxy chains en Java EE/Servlet API
Cómo Spring Security utiliza la cadena de filtros (Filter Chain Proxy)
Explicación del DelegatingFilterProxy y su rol

Componentes Arquitectónicos Clave
Spring Security está construido sobre una arquitectura modular donde cada componente tiene un rol específico dentro del flujo de autenticación y autorización. Conocer estos componentes ayuda a comprender qué sucede detrás de escena, cómo personalizar la seguridad de manera efectiva y cómo adaptarla a arquitecturas modernas basadas en stateless y tokens JWT.
Security Filter Chain y la necesidad de filtros personalizados
El Security Filter Chain es la columna vertebral de Spring Security. Cada solicitud HTTP pasa por esta cadena de filtros, que pueden autenticar, autorizar, manejar excepciones o modificar la request/response.
En arquitecturas stateless, donde se usan JWT, es necesario crear filtros personalizados que:
-
Intercepten cada solicitud entrante.
-
Extraigan y validen el token JWT del header Authorization.
-
Construyan el SecurityContext temporal para esa solicitud.
Sin este filtro personalizado, Spring Security no puede reconocer automáticamente el usuario ni sus roles a partir del token, porque la autenticación no depende de una sesión HTTP.
Ejemplo de Security Filter Chain en código
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
// Filtro personalizado JWT antes de UsernamePasswordAuthenticationFilter
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
-
addFilterBefore: indica dónde insertar el filtro personalizado en la cadena de filtros.
-
Se recomienda colocarlo antes de UsernamePasswordAuthenticationFilter para que la autenticación basada en token se procese primero y se resuelva rápido el security context.
Filtros principales del Security Filter Chain
-
UsernamePasswordAuthenticationFilter: maneja la autenticación por formulario (stateful).
-
BasicAuthenticationFilter: autenticación HTTP Basic, útil en APIs simples.
-
ExceptionTranslationFilter: captura y maneja errores de seguridad (401, 403).
-
FilterSecurityInterceptor: realiza la autorización final mediante AccessDecisionManager.
En arquitecturas stateless, muchos de estos filtros siguen presentes, pero la carga y persistencia del SecurityContext se hace a partir del JWT y no de la sesión.
SecurityContext y reconstrucción a partir de JWT
-
El SecurityContext contiene la información del usuario y sus permisos.
-
En arquitecturas stateless, un filtro personalizado reconstruye este contexto en cada solicitud a partir del token.
Con esta arquitectura stateless, donde cada request reconstruye el SecurityContext a partir del JWT y no depende de una sesión almacenada en el servidor, la aplicación se vuelve mucho más escalable. Cada instancia del servicio puede procesar solicitudes de manera independiente, facilitando la escalabilidad horizontal, la integración con microservicios y la tolerancia a fallos, sin necesidad de compartir estado entre servidores.
AuthenticationManager y UserDetailsService
-
El AuthenticationManager valida las credenciales.
-
Para JWT, un JwtAuthenticationProvider puede validar el token y crear el Authentication.
-
UserDetailsService puede usarse opcionalmente para verificar que el usuario aún existe o tiene permisos actualizados, incluso si la información básica ya está en el token.
Flujo de autenticación y autorización.
-
Entrada de la request en el contenedor de servlets
- La request HTTP entra al contenedor de servlets (por ejemplo Tomcat) y llega al DispatcherServlet.
-
Antes de ser procesada por el controlador, atraviesa la Security Filter Chain, que está registrada mediante DelegatingFilterProxy → FilterChainProxy.
-
La cadena contiene todos los filtros de Spring Security configurados (autenticación, autorización, excepciones, etc.).
-
Paso por filtros de autenticación (sin token)
-
Los filtros como JwtFilter o UsernamePasswordAuthenticationFilter examinan la request para determinar si pueden autenticar al usuario.
-
No hay token JWT ni credenciales en la request.
-
Por tanto, los filtros no pueden autenticar y delegan la request al siguiente filtro de la cadena.
-
-
Último filtro de la cadena: FilterSecurityInterceptor
-
El FilterSecurityInterceptor es el último filtro de la Security Filter Chain.
-
Su rol principal es verificar si el usuario tiene permisos para acceder al recurso solicitado, consultando el SecurityContext y los roles/authorities requeridos.
-
Como la ruta está marcada como permitAll, incluso sin que haya un Authentication en el SecurityContext, permite que la request continúe hacia el controlador.
-
-
Controlador REST como entry point de login
-
La request finalmente llega al controlador de login (/auth/login).
-
El controlador extrae las credenciales del body (username/password) y arma un Authentication no autenticado:
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); -
-
Delegación al AuthenticationManager
- El controlador envía este authRequest al AuthenticationManager:
Authentication authResult = authenticationManager.authenticate(authRequest);-
Aquí entra DaoAuthenticationProvider (u otro provider configurado) para validar las credenciales y construir un Authentication autenticado.
-
Si la autenticación falla, se lanza una excepción y se devuelve un error 401 al cliente.
-
Generación del JWT y respuesta al cliente
-
Si la autenticación es correcta, el controlador genera un JWT que incluye claims del usuario (roles, permisos, expiración) y lo devuelve al cliente en la response body.
-
Este token permitirá al cliente autenticarse en requests posteriores de manera stateless.
-
-
Requests futuras con JWT
- El cliente envía el JWT en el header Authorization: Bearer
.
La request atraviesa nuevamente la Security Filter Chain, pero ahora:
-
El JwtFilter o JwtAuthenticationProvider detecta el token.
-
Valida la firma y expiración del JWT.
-
Reconstruye un Authentication autenticado y lo almacena en el SecurityContext.
-
Luego, el FilterSecurityInterceptor revisa los roles/authorities del SecurityContext para autorizar la request antes de llegar al controlador.
- El cliente envía el JWT en el header Authorization: Bearer
A continuación se presentan diagramas de flujo que muestran lo explicado anteriormente:
Flujo de autenticación

Flujo de autorización
