Spring Security Essentials Components
Spring Security: Essentials Components
Arquitectura General
En una aplicación Spring Boot tradicional sin seguridad, las peticiones HTTP llegan directamente a los controladores, se procesa la lógica de negocio y se devuelve una respuesta. Sin embargo, cuando integramos Spring Security, se introduce una capa intermedia de filtros de seguridad que interceptan y procesan cada petición antes de que llegue a nuestros controladores.
Flujo de una Petición en Spring Security
REQUEST → Filter Chain → Controllers → RESPONSE

Componentes Esenciales
1. Filter Chain (Cadena de Filtros)
La Filter Chain es una secuencia ordenada de filtros por los que debe pasar cada petición HTTP. Estos filtros se ejecutan antes de que la petición llegue al controlador.
Características importantes:
- Incluso sin Spring Security, las aplicaciones Spring Boot tienen filtros, pero no son filtros relacionados con seguridad
- Al configurar Spring Security, se añaden filtros específicos de seguridad a la cadena
- Cada filtro tiene una responsabilidad específica y procesa la petición de manera secuencial
- El número y tipo de filtros depende de la configuración de seguridad implementada
Tipos de filtros comunes en Spring Security:
- UsernamePasswordAuthenticationFilter: Procesa intentos de autenticación basados en formularios
- BasicAuthenticationFilter: Maneja autenticación HTTP Basic
- JwtAuthenticationFilter: Procesa tokens JWT (cuando está configurado)
- CsrfFilter: Protege contra ataques CSRF
- CorsFilter: Gestiona políticas CORS
- ExceptionTranslationFilter: Traduce excepciones de seguridad
2. Authentication Filter (Filtro de Autenticación)
El Authentication Filter es un filtro especializado que intercepta las peticiones de inicio de sesión (login).
Responsabilidades:
- Detectar peticiones de autenticación (típicamente a endpoints como
/login) - Extraer las credenciales del usuario (username, password u otros datos de autenticación)
- Crear un objeto
Authenticationinicial con las credenciales proporcionadas - Delegar el proceso de validación al Authentication Manager
Flujo de trabajo:
- Usuario envía credenciales (username y password)
- El filtro intercepta la petición
- Extrae las credenciales de la petición
- Crea un objeto
Authentication(inicialmente no autenticado) - Pasa este objeto al Authentication Manager para su validación
3. Authentication Object (Objeto de Autenticación)
El Authentication Object es la representación central de la información de seguridad en Spring Security.
¿Qué contiene?
- Principal: Identidad del usuario (generalmente el username)
- Credentials: Credenciales (password, que se elimina después de la autenticación exitosa)
- Authorities: Lista de permisos/roles del usuario
- Authenticated: Bandera que indica si la autenticación ha sido validada
- Details: Información adicional sobre la petición (IP, sesión, etc.)
Estados del objeto:
- No autenticado: Contiene solo las credenciales proporcionadas por el usuario
- Autenticado: Completamente poblado con roles, permisos y toda la información del usuario validado
4. Authentication Manager (Gestor de Autenticación)
El Authentication Manager es el coordinador central del proceso de autenticación.
Responsabilidades:
- Recibir el objeto
Authenticationdel filtro - Decidir qué estrategia de autenticación utilizar
- Delegar la validación real a un Authentication Provider apropiado
- Devolver el objeto
Authenticationautenticado (o lanzar una excepción si falla)
Implementación principal:
La implementación más común es ProviderManager, que mantiene una lista de Authentication Providers y los consulta secuencialmente hasta encontrar uno que pueda procesar la petición.
// Método principal
Authentication authenticate(Authentication authentication) throws AuthenticationException
5. Authentication Provider (Proveedor de Autenticación)
El Authentication Provider es el componente que realmente valida las credenciales del usuario.
Responsabilidad principal:
Verificar si las credenciales proporcionadas son correctas comparándolas con los datos almacenados en el sistema.
Para realizar esta validación, el Authentication Provider necesita:
- UserDetailsService: Para cargar los datos del usuario desde la fuente de datos
- PasswordEncoder: Para comparar contraseñas encriptadas
Tipos de Authentication Providers:
DaoAuthenticationProvider
El más utilizado. Valida usuarios contra una base de datos o cualquier fuente de datos accesible mediante DAO (Data Access Object).
Flujo:
- Recibe el objeto
Authenticationcon las credenciales - Usa
UserDetailsServicepara cargar los detalles del usuario - Usa
PasswordEncoderpara comparar la contraseña proporcionada con la almacenada - Si coinciden, crea un objeto
Authenticationcompleto con roles y permisos - Devuelve el objeto autenticado
InMemoryAuthenticationProvider
Utilizado para autenticación contra usuarios almacenados en memoria. Útil para:
- Entornos de desarrollo
- Prototipos
- Aplicaciones con usuarios predefinidos
LdapAuthenticationProvider
Para integración con servidores LDAP (Lightweight Directory Access Protocol). Común en entornos empresariales donde la gestión de usuarios está centralizada.
JdbcAuthenticationProvider
Autenticación directa contra base de datos usando consultas JDBC personalizadas.
Métodos principales:
// Realiza la autenticación
Authentication authenticate(Authentication authentication) throws AuthenticationException
// Indica si este provider soporta un tipo específico de Authentication
boolean supports(Class<?> authentication)
6. UserDetailsService (Servicio de Detalles del Usuario)
El UserDetailsService es responsable de cargar la información del usuario desde la fuente de datos.
Responsabilidad:
Recuperar toda la información necesaria sobre un usuario específico, incluyendo:
- Username
- Password (encriptado)
- Roles y authorities
- Estado de la cuenta (bloqueada, expirada, habilitada, etc.)
Método principal:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
Proceso:
- Recibe un username como parámetro
- Consulta la fuente de datos (base de datos, LDAP, API externa, etc.)
- Convierte la información del usuario en un objeto
UserDetails - Devuelve el objeto
UserDetailsal Authentication Provider
Implementaciones comunes:
- JdbcUserDetailsManager: Carga usuarios desde base de datos usando JDBC
- InMemoryUserDetailsManager: Gestiona usuarios en memoria
- LdapUserDetailsService: Carga usuarios desde servidor LDAP
- Implementación personalizada: Para lógicas de negocio específicas
Ejemplo de implementación personalizada:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Usuario no encontrado"));
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles().toArray(new String[0]))
.accountExpired(!user.isAccountNonExpired())
.accountLocked(!user.isAccountNonLocked())
.credentialsExpired(!user.isCredentialsNonExpired())
.disabled(!user.isEnabled())
.build();
}
}
7. UserDetails (Objeto de Detalles del Usuario)
El UserDetails es una interfaz que representa al usuario autenticado.
Información que contiene:
- Username: Identificador único del usuario
- Password: Contraseña encriptada
- Authorities: Colección de permisos/roles
- Estado de la cuenta:
isAccountNonExpired(): ¿La cuenta no ha expirado?isAccountNonLocked(): ¿La cuenta no está bloqueada?isCredentialsNonExpired(): ¿Las credenciales no han expirado?isEnabled(): ¿La cuenta está habilitada?
8. PasswordEncoder (Codificador de Contraseñas)
El PasswordEncoder es fundamental para la seguridad de las contraseñas.
Responsabilidades:
- Encriptar contraseñas en texto plano antes de almacenarlas
- Comparar contraseñas encriptadas durante la autenticación
- Usar algoritmos seguros de hashing
Métodos principales:
// Encripta una contraseña en texto plano
String encode(CharSequence rawPassword)
// Compara una contraseña sin encriptar con una encriptada
boolean matches(CharSequence rawPassword, String encodedPassword)
Implementaciones comunes:
- BCryptPasswordEncoder: Usa BCrypt (recomendado, adaptativo)
- Argon2PasswordEncoder: Usa Argon2 (más moderno y seguro)
- Pbkdf2PasswordEncoder: Usa PBKDF2
- SCryptPasswordEncoder: Usa SCrypt
NoOpPasswordEncoder: Sin encriptación (DEPRECATED, solo para desarrollo)
Ejemplo de configuración:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // 12 rondas de hashing
}
9. Security Context (Contexto de Seguridad)
El Security Context es un contenedor que almacena la información de seguridad de la petición actual.
Características:
- Almacena el objeto
Authenticationdel usuario autenticado - Está disponible durante todo el ciclo de vida de la petición
- Es thread-safe y se gestiona por petición HTTP
- Permite acceder a la información del usuario en cualquier capa de la aplicación
Acceso al Security Context:
// Obtener el usuario autenticado
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
// Verificar si el usuario está autenticado
if (authentication != null && authentication.isAuthenticated()) {
// Usuario autenticado
}
Estrategias de almacenamiento:
- MODE_THREADLOCAL: Una instancia por thread (por defecto)
- MODE_INHERITABLETHREADLOCAL: Se hereda a threads hijos
- MODE_GLOBAL: Una única instancia global (raramente usado)
Flujo Completo de Autenticación: Paso a Paso
Veamos el proceso completo de autenticación cuando un usuario intenta iniciar sesión:
Paso 1: Envío de Credenciales
El usuario envía una petición de login con su username y password (por ejemplo, a través de un formulario web o petición POST).
POST /login
Content-Type: application/x-www-form-urlencoded
username=juan&password=miPassword123
Paso 2: Interceptación por el Authentication Filter
- La petición pasa por la cadena de filtros
- El Authentication Filter (como
UsernamePasswordAuthenticationFilter) intercepta la petición - Extrae las credenciales: username y password
- Crea un objeto
Authenticationinicial (aún no autenticado)
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
Paso 3: Delegación al Authentication Manager
El filtro pasa el objeto Authentication al Authentication Manager.
Authentication authResult = authenticationManager.authenticate(authRequest);
Paso 4: Selección del Authentication Provider
El Authentication Manager (típicamente ProviderManager):
- Revisa su lista de Authentication Providers
- Selecciona el apropiado (generalmente
DaoAuthenticationProvider) - Delega la validación a ese provider
Paso 5: Carga de Datos del Usuario
El Authentication Provider llama al UserDetailsService:
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
El UserDetailsService:
- Consulta la base de datos (o fuente de datos correspondiente)
- Busca el usuario por username
- Carga toda la información: password encriptado, roles, estado de cuenta
- Devuelve un objeto
UserDetails
Paso 6: Validación de la Contraseña
El Authentication Provider usa el PasswordEncoder para comparar contraseñas:
boolean passwordMatch = passwordEncoder.matches(
rawPassword, // Contraseña proporcionada por el usuario
userDetails.getPassword() // Contraseña encriptada almacenada
);
Paso 7: Validaciones Adicionales
El provider verifica también:
- ¿La cuenta está habilitada? (
isEnabled()) - ¿La cuenta no está bloqueada? (
isAccountNonLocked()) - ¿Las credenciales no han expirado? (
isCredentialsNonExpired()) - ¿La cuenta no ha expirado? (
isAccountNonExpired())
Paso 8: Creación del Authentication Object Completo
Si todas las validaciones pasan, el Authentication Provider:
- Crea un nuevo objeto
Authenticationcompletamente poblado - Incluye el username, authorities (roles/permisos)
- Marca el objeto como autenticado
- Importante: Por seguridad, se elimina la contraseña del objeto
Authentication authenticatedToken = new UsernamePasswordAuthenticationToken(
userDetails,
null, // La contraseña se elimina por seguridad
userDetails.getAuthorities()
);
Paso 9: Retorno al Authentication Manager y Filter
- El objeto autenticado vuelve al Authentication Manager
- Este lo devuelve al Authentication Filter
Paso 10: Establecimiento del Security Context
El Authentication Filter:
- Almacena el objeto
Authenticationen el Security Context - Este contexto estará disponible durante toda la petición
SecurityContextHolder.getContext().setAuthentication(authenticatedToken);
Paso 11: Continuación de la Petición
- La petición continúa por la cadena de filtros
- Eventualmente llega al Controller
- El código de la aplicación se ejecuta con el usuario ya autenticado
Paso 12: Autorización Durante la Petición
Durante toda la petición:
- Los filtros de autorización consultan el Security Context
- Verifican si el usuario tiene los roles/permisos necesarios
- Permiten o deniegan el acceso a recursos según la configuración
@PreAuthorize("hasRole('ADMIN')")
public void adminOperation() {
// Solo usuarios con rol ADMIN pueden ejecutar esto
}
Autorización: Roles y Permisos
Una vez autenticado el usuario, Spring Security gestiona la autorización, determinando qué puede y qué no puede hacer el usuario en el sistema.
Roles vs Authorities
- Authority: Permiso individual (ej: “READ_USERS”, “DELETE_POST”)
- Role: Conjunto de authorities (ej: “ROLE_ADMIN”, “ROLE_USER”)
Convención: Los roles en Spring Security se prefijan con “ROLE_”
Configuración de Autorización
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/api/**").hasAuthority("API_ACCESS")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
}
Autorización a Nivel de Método
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long userId) {
// Solo ADMIN puede eliminar usuarios
}
@PreAuthorize("#username == authentication.principal.username or hasRole('ADMIN')")
public User getUserDetails(String username) {
// Un usuario puede ver sus propios detalles, o un ADMIN puede ver cualquiera
}
@PostAuthorize("returnObject.owner == authentication.principal.username")
public Document getDocument(Long id) {
// Solo el propietario del documento puede acceder
}
}
Ejemplo de Configuración Completa
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
}
Diagrama de Flujo Resumido
1. REQUEST → Filter Chain
2. Authentication Filter
↓
Extrae credenciales
↓
Crea Authentication Object (no autenticado)
3. Authentication Manager
↓
Delega a Authentication Provider
4. Authentication Provider
↓
├─→ UserDetailsService.loadUserByUsername()
│ ↓
│ Database → UserDetails
│
└─→ PasswordEncoder.matches()
↓
Valida contraseña
5. Si es válido:
↓
Crea Authentication Object (autenticado con roles)
↓
Retorna a Authentication Manager
↓
Retorna a Authentication Filter
6. Security Context se establece
↓
SecurityContextHolder.setContext()
7. Request continúa → Controller
↓
Durante la petición: autorización basada en roles
8. RESPONSE
Mejores Prácticas
1. Seguridad de Contraseñas
- Siempre usa un PasswordEncoder fuerte (BCrypt, Argon2)
- Nunca almacenes contraseñas en texto plano
- Configura suficientes rondas de hashing (BCrypt: 10-12)
2. Gestión de Sesiones
http.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Para APIs REST
.maximumSessions(1) // Limitar sesiones concurrentes
.expiredUrl("/login?expired")
);
3. Protección CSRF
- Habilita CSRF para aplicaciones web con formularios
- Desactívalo solo para APIs stateless con autenticación por token
4. CORS
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://miapp.com"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
5. Logging y Auditoría
@Component
public class AuthenticationEventListener {
@EventListener
public void onAuthenticationSuccess(AuthenticationSuccessEvent event) {
log.info("Usuario autenticado: {}", event.getAuthentication().getName());
}
@EventListener
public void onAuthenticationFailure(AuthenticationFailureBadCredentialsEvent event) {
log.warn("Intento fallido de autenticación: {}", event.getAuthentication().getName());
}
}
Recursos adicionales:
- Documentación oficial: https://spring.io/projects/spring-security
- Guías de referencia: https://docs.spring.io/spring-security/reference/
- Repositorio GitHub: https://github.com/spring-projects/spring-security