Spring Boot

SpringBoot

Tutorial SpringBoot: Introducción a Spring Security

Aprende a agregar Spring Security en proyectos de Spring Boot para seguridad en tus aplicaciones web MVC y API REST. Aprende los tipos UserDetails, UserDetailsService y más.

Aprende SpringBoot GRATIS y certifícate

Qué es Spring Security, arquitectura de Spring Security y starters

Spring Security es un framework especializado en proporcionar autenticación y autorización en aplicaciones Java, especialmente aquellas construidas con el ecosistema Spring. Ofrece un conjunto robusto y extensible de funcionalidades para proteger aplicaciones web y servicios REST, abordando preocupaciones comunes como el control de acceso, la gestión de sesiones y la protección contra ataques como Cross-Site Request Forgery (CSRF).

La arquitectura de Spring Security se basa en una serie de componentes clave que trabajan juntos para garantizar la seguridad de una aplicación:

  • SecurityContext: es el contenedor donde se almacena la información de seguridad en el contexto actual de la aplicación. Incluye detalles sobre el usuario autenticado y sus credenciales.
  • Authentication: representa la información de autenticación de un usuario. Contiene detalles como el principal (generalmente el nombre de usuario), las credenciales (como la contraseña) y las autoridades o roles asignados.
  • Authorization: se encarga de verificar si un usuario autenticado tiene los permisos necesarios para acceder a ciertos recursos o realizar operaciones específicas.
  • Security Filters: Spring Security utiliza una cadena de filtros (Security Filter Chain) que intercepta las solicitudes entrantes y aplica las reglas de seguridad definidas. Cada filtro tiene una responsabilidad específica, como la gestión de sesiones o la validación de tokens.
  • AccessDecisionManager: decide si una autenticación particular tiene acceso a un recurso protegido, basándose en las políticas de seguridad configuradas.

La interacción de estos componentes sigue un flujo definido: cuando una solicitud llega a la aplicación, pasa a través de la cadena de filtros de seguridad. Si la solicitud requiere autenticación, el AuthenticationManager procesa las credenciales proporcionadas y, si son válidas, se guarda la autenticación en el SecurityContext. Posteriormente, el AccessDecisionManager verifica los permisos para autorizar o denegar el acceso al recurso solicitado.

La integración de Spring Security en aplicaciones Spring Boot se ha simplificado gracias a los starters. Un starter es una dependencia que incluye todas las librerías necesarias y proporciona una configuración básica para empezar a trabajar con una funcionalidad específica. En el caso de Spring Security, el starter de seguridad configura automáticamente las protecciones básicas al agregar la siguiente dependencia en el archivo pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Al incluir este starter, la aplicación activa por defecto medidas de seguridad que requieren autenticación para todas las rutas y generan una contraseña aleatoria para el usuario predeterminado. Esto garantiza que, desde el inicio, la aplicación no esté expuesta sin protecciones básicas.

Para personalizar la configuración de seguridad, es común definir una clase anotada con @Configuration. En esta clase, se puede crear un bean de tipo SecurityFilterChain que permite adaptar las reglas de seguridad a las necesidades de la aplicación:

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(login -> login
                .loginPage("/login")
                .permitAll()
            )
            .logout(logout -> logout
                .permitAll()
            );
        return http.build();
    }
}

En este ejemplo, se utiliza el objeto HttpSecurity para configurar:

  • Autorización: se permite el acceso sin autenticación a las rutas que coinciden con /public/, mientras que cualquier otra solicitud requiere que el usuario esté autenticado.
  • Autenticación: se habilita un formulario de inicio de sesión personalizado en la ruta /login.
  • Cierre de sesión: se configura el logout para permitir que los usuarios cierren sesión.

La configuración fluida que proporciona HttpSecurity facilita la lectura y mantenimiento de las reglas de seguridad. Las lambdas utilizadas en esta configuración son una característica de Java moderna que mejora la legibilidad del código.

Spring Security también soporta múltiples métodos de autenticación, incluyendo autenticación basada en tokens JWT, autenticación OAuth2, y autenticación con proveedores externos. Esto se logra extendiendo o reemplazando los componentes predeterminados, como el AuthenticationProvider o el UserDetailsService, para adaptarlos a los requisitos específicos.

Por ejemplo, para implementar una autenticación con JWT, se puede agregar un filtro personalizado en la cadena de filtros de seguridad que:

  1. Intercepte las solicitudes entrantes.
  2. Extraiga el token JWT del encabezado de autorización.
  3. Valide el token y extraiga la información de autenticación.
  4. Establezca la autenticación en el SecurityContext.

La modularidad de Spring Security permite integrar estos componentes sin alterar la estructura básica de la aplicación. Además, al apoyarse en las capacidades de inyección de dependencias de Spring, es posible injectar servicios y repositorios necesarios en los componentes de seguridad.

Otro aspecto importante es la protección contra amenazas comunes de seguridad web. Spring Security proporciona medidas integradas para mitigar ataques como XSS (Cross-Site Scripting), Clickjacking y CSRF. Por ejemplo, la protección CSRF está habilitada por defecto en aplicaciones web que utilizan métodos como POST, PUT, DELETE, etc. Si una aplicación está diseñada como una API REST sin estado, es posible desactivar esta protección si se considera apropiado:

http
    .csrf(csrf -> csrf.disable());

Es fundamental entender las implicaciones de seguridad al modificar estas configuraciones y asegurarse de que las aplicaciones permanezcan protegidas frente a vulnerabilidades conocidas.

En el contexto de aplicaciones reactivas construidas con Spring WebFlux, Spring Security proporciona un conjunto de herramientas adaptadas para el paradigma reactivo. Utiliza componentes asíncronos y no bloqueantes, y trabaja con el Reactor Context para propagar la información de seguridad a través de los flujos reactivos. La configuración sigue siendo similar, pero utiliza clases y métodos específicos para aplicaciones reactivas.

Explicación de UserDetails, UserDetailsService

En el marco de Spring Security, las interfaces UserDetails y UserDetailsService desempeñan un papel esencial en el proceso de autenticación de usuarios. Estas interfaces permiten al framework obtener la información necesaria sobre los usuarios para validar sus credenciales y establecer su identidad dentro de la aplicación.

La interfaz UserDetails es una representación detallada de un usuario. Proporciona métodos para acceder a información clave como el nombre de usuario, la contraseña y las autoridades o roles asignados. Además, permite verificar el estado de la cuenta mediante métodos que indican si la cuenta está expirada, bloqueada o deshabilitada.

Por ejemplo, una implementación personalizada de UserDetails podría ser:

public class UsuarioDetalles implements UserDetails {

    private final Usuario usuario;

    public UsuarioDetalles(Usuario usuario) {
        this.usuario = usuario;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return usuario.getRoles().stream()
            .map(rol -> new SimpleGrantedAuthority(rol.getNombre()))
            .toList();
    }

    @Override
    public String getPassword() {
        return usuario.getContraseña();
    }

    @Override
    public String getUsername() {
        return usuario.getNombreUsuario();
    }

    @Override
    public boolean isAccountNonExpired() {
        return usuario.isCuentaNoExpirada();
    }

    @Override
    public boolean isAccountNonLocked() {
        return usuario.isCuentaNoBloqueada();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return usuario.isCredencialesNoExpiradas();
    }

    @Override
    public boolean isEnabled() {
        return usuario.isHabilitado();
    }
}

En este código, se adapta la entidad Usuario de la aplicación al modelo de seguridad que Spring Security espera, implementando los métodos requeridos por la interfaz UserDetails.

La interfaz UserDetailsService, por su parte, es responsable de suministrar la información del usuario al proceso de autenticación. Tiene un único método, loadUserByUsername(String username), que carga los detalles del usuario basándose en el nombre de usuario proporcionado.

Una implementación típica de UserDetailsService podría ser:

@Service
public class UsuarioDetallesService implements UserDetailsService {

    private final UsuarioRepositorio usuarioRepositorio;

    public UsuarioDetallesService(UsuarioRepositorio usuarioRepositorio) {
        this.usuarioRepositorio = usuarioRepositorio;
    }

    @Override
    public UserDetails loadUserByUsername(String nombreUsuario) throws UsernameNotFoundException {
        Usuario usuario = usuarioRepositorio.findByNombreUsuario(nombreUsuario)
            .orElseThrow(() -> new UsernameNotFoundException("Usuario no encontrado"));
        return new UsuarioDetalles(usuario);
    }
}

En este ejemplo, el servicio utiliza un repositorio para acceder a la base de datos y obtener la entidad Usuario correspondiente al nombre de usuario. Si el usuario existe, se devuelve una instancia de UsuarioDetalles; de lo contrario, se lanza una excepción UsernameNotFoundException.

Al implementar UserDetailsService, es crucial manejar adecuadamente las excepciones y asegurar que la información del usuario se carga de manera eficiente. Utilizar Optional y métodos como orElseThrow garantiza que se gestionen correctamente los casos en los que el usuario no exista.

La interfaz GrantedAuthority es otro componente clave en este proceso. Representa una autorización otorgada al usuario, generalmente asociada a un rol o permiso. En nuestra implementación de UserDetails, el método getAuthorities retorna una colección de GrantedAuthority que Spring Security utiliza para realizar la autorización.

Por ejemplo, si el usuario tiene asignado el rol "ADMIN", el método podría mapear este rol a una autoridad:

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return usuario.getRoles().stream()
        .map(rol -> new SimpleGrantedAuthority("ROLE_" + rol.getNombre()))
        .toList();
}

Es una práctica recomendada prefijar los roles con "ROLE_" para mantener la consistencia y facilitar las configuraciones de seguridad.

Además, es fundamental codificar adecuadamente las contraseñas antes de almacenarlas o compararlas. Spring Security proporciona interfaces como PasswordEncoder para este propósito. Al almacenar usuarios, se debe asegurar que las contraseñas estén cifradas utilizando un algoritmo seguro como BCrypt.

En el contexto de autenticación, el flujo es el siguiente:

  1. El usuario intenta autenticarse proporcionando sus credenciales.
  2. Spring Security delega en el AuthenticationManager para autenticar al usuario.
  3. El AuthenticationManager utiliza el UserDetailsService para cargar los detalles del usuario.
  4. Se compara la contraseña proporcionada con la almacenada utilizando un PasswordEncoder.
  5. Si la autenticación es exitosa, se establece un Authentication en el SecurityContext.

Esta arquitectura modular permite que los desarrolladores personalicen y amplíen el proceso de autenticación según las necesidades de la aplicación. Implementar correctamente UserDetails y UserDetailsService es esencial para garantizar un sistema de seguridad robusto y flexible.

Al aprovechar las interfaces proporcionadas por Spring Security, se puede integrar fácilmente con diferentes fuentes de datos, como bases de datos relacionales, servicios LDAP o sistemas externos. Esto ofrece la flexibilidad necesaria para adaptarse a diversos escenarios empresariales y requisitos de seguridad.

Explicación de SecurityContextHolder, Authentication, Principal y GrantedAuthority

En Spring Security, el flujo de seguridad se basa en varios componentes clave que gestionan la autenticación y autorización de usuarios en una aplicación. Entre estos componentes destacan SecurityContextHolder, Authentication, Principal y GrantedAuthority. Comprender cómo interactúan es esencial para implementar mecanismos de seguridad eficaces y flexibles.

El SecurityContextHolder es el núcleo donde se almacena el SecurityContext, que a su vez contiene la información de seguridad del usuario actualmente autenticado. Este contexto es accesible en cualquier parte de la aplicación y permite obtener detalles sobre la identidad del usuario y sus autorizaciones. Por defecto, el SecurityContextHolder utiliza una estrategia de almacenamiento basada en hilos, lo que significa que el contexto de seguridad está asociado al ThreadLocal actual.

Para acceder al Authentication actual, que representa el token de autenticación del usuario, se puede utilizar el siguiente código:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

El objeto Authentication contiene información relevante sobre el usuario autenticado. Incluye detalles como el principal, las credenciales y las autoridades concedidas. El principal generalmente representa la identidad del usuario, que suele ser un objeto que implementa la interfaz UserDetails. Sin embargo, también puede ser simplemente el nombre de usuario o cualquier otro objeto que identifique al usuario dentro del sistema.

Las GrantedAuthority son las autoridades o permisos asignados al usuario. Representan las acciones que el usuario está autorizado a realizar dentro de la aplicación. Las GrantedAuthority se utilizan en los procesos de autorización para determinar si el usuario tiene permiso para acceder a ciertos recursos o ejecutar operaciones específicas.

Un ejemplo común para obtener el nombre de usuario y las autoridades del usuario autenticado es:

String nombreUsuario = authentication.getName(); // Obtiene el principal
Collection<? extends GrantedAuthority> autoridades = authentication.getAuthorities();

En este contexto, authentication.getName() devuelve el nombre del usuario, mientras que getAuthorities() proporciona una colección de las autoridades asignadas al usuario.

Es importante destacar que el SecurityContextHolder facilita el acceso al contexto de seguridad desde cualquier punto de la aplicación, lo que es especialmente útil en situaciones donde no se dispone del mecanismo de inyección de dependencias de Spring. Por ejemplo, en una clase utilitaria o en código que no es administrado por el contenedor de Spring, se puede acceder al usuario autenticado directamente:

public static UsuarioActual obtenerUsuarioActual() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication != null && authentication.isAuthenticated()) {
        return (UsuarioActual) authentication.getPrincipal();
    }
    return null;
}

En este fragmento, se verifica si el Authentication no es nulo y si el usuario está autenticado antes de obtener el principal y convertirlo al tipo UsuarioActual, que es una clase que representa al usuario en la aplicación.

El Principal puede ser cualquier objeto que contenga la información sobre el usuario. En muchos casos, es una instancia de UserDetails, pero también puede ser un objeto personalizado. Esto permite almacenar información adicional sobre el usuario, como su nombre completo, dirección de correo electrónico u otros datos relevantes.

Las GrantedAuthority son fundamentales para el proceso de autorización en Spring Security. Cada GrantedAuthority representa un permiso o rol, y se suelen implementar utilizando la clase SimpleGrantedAuthority. Por ejemplo:

List<GrantedAuthority> autoridades = List.of(new SimpleGrantedAuthority("ROLE_ADMIN"), new SimpleGrantedAuthority("PERMISO_ESPECIAL"));

En este ejemplo, el usuario tiene asignados dos permisos: ROLE_ADMIN y PERMISO_ESPECIAL. Al definir las autoridades de esta manera, se facilita la gestión de roles y permisos en la aplicación, permitiendo control granular sobre el acceso a recursos.

Durante el proceso de autenticación, Spring Security construye un objeto Authentication que contiene el principal y las autoridades. Una vez autenticado el usuario, este objeto se almacena en el SecurityContextHolder, lo que permite que la información de seguridad esté disponible en toda la aplicación.

Es posible personalizar el comportamiento de SecurityContextHolder cambiando su estrategia de almacenamiento. Por defecto, utiliza ThreadLocal, pero se puede configurar para utilizar estrategias alternativas como InheritableThreadLocal o incluso almacenamiento en el contexto de la sesión HTTP. Esto se logra mediante el método setStrategyName:

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

Esta flexibilidad es útil en aplicaciones que manejan múltiples hilos o requieren compartir el contexto de seguridad entre diferentes hilos de ejecución.

En situaciones donde se necesita establecer manualmente el Authentication en el SecurityContextHolder, como en autenticaciones personalizadas o pruebas unitarias, se puede hacer de la siguiente manera:

UserDetails usuario = userDetailsService.loadUserByUsername("usuario");
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(usuario, null, usuario.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);

Aquí, se crea un AuthenticationToken con el UserDetails del usuario y sus autoridades, y se establece en el SecurityContext. Esto simula el proceso de autenticación y es útil para casos específicos donde se requiere una autenticación manual.

Es fundamental comprender que la seguridad en Spring se basa en estos componentes para garantizar que cada solicitud se realice en el contexto de un usuario autenticado y autorizado. Las autorizaciones se pueden definir utilizando expresiones que evalúan las GrantedAuthority del usuario. Por ejemplo, en un método controlador se puede usar:

@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public String vistaAdmin() {
    // Lógica para usuarios con rol ADMIN
    return "admin";
}

Esta anotación @PreAuthorize verifica que el usuario tenga la autoridad correspondiente antes de permitir el acceso al método.

Además, en las vistas de Thymeleaf, es posible mostrar u ocultar contenido en función de las autoridades del usuario utilizando las expresiones de seguridad de Spring:

<div sec:authorize="hasAuthority('ROLE_ADMIN')">
    <p>Contenido para administradores</p>
</div>

Esto permite crear interfaces dinámicas y seguras que responden al perfil de cada usuario.

En resumen, el SecurityContextHolder, el Authentication, el Principal y las GrantedAuthority son pilares sobre los que se construye el sistema de seguridad en Spring. Manejar adecuadamente estos componentes es esencial para desarrollar aplicaciones seguras y confiables, donde la autenticación y autorización se gestionan de manera coherente y eficiente.

Además, es recomendable familiarizarse con las mejores prácticas en la gestión del SecurityContextHolder, especialmente en aplicaciones que utilizan hilos múltiples o asíncronos. En contextos reactivos o cuando se utilizan métodos como @Async, es necesario asegurarse de que el contexto de seguridad se propaga correctamente.

Finalmente, recordar que la seguridad es un aspecto crítico en cualquier aplicación y que el entendimiento profundo de estos conceptos contribuye significativamente a la creación de sistemas robustos y protegidos frente a posibles vulnerabilidades.

Explicación de PasswordEncoder y BCryptPasswordEncoder

En el contexto de Spring Security, gestionar las contraseñas de forma segura es fundamental para proteger las aplicaciones contra accesos no autorizados. Para ello, se utiliza la interfaz PasswordEncoder, que define métodos para codificar y verificar contraseñas de manera segura y eficiente.

La interfaz PasswordEncoder proporciona dos métodos principales:

  • String encode(CharSequence rawPassword): codifica una contraseña en texto plano.
  • boolean matches(CharSequence rawPassword, String encodedPassword): verifica si una contraseña en texto plano coincide con una contraseña codificada.

Estos métodos permiten que las aplicaciones no almacenen contraseñas en texto plano, sino que las guarden de forma cifrada, aumentando así la seguridad de los datos de los usuarios.

Una implementación común de PasswordEncoder es BCryptPasswordEncoder, que utiliza el algoritmo BCrypt para codificar las contraseñas. BCrypt es un algoritmo de hash adaptativo que incorpora una función de salting y un factor de coste, lo que lo hace resistente a ataques como fuerza bruta o rainbow tables.

Para utilizar BCryptPasswordEncoder en una aplicación Spring Boot, se puede definir un bean en la clase de configuración de seguridad:

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

Este bean estará disponible para inyección en otras partes de la aplicación donde sea necesario codificar o verificar contraseñas. Por ejemplo, al crear un nuevo usuario, se debe codificar su contraseña antes de almacenarla en la base de datos:

@Service
public class UsuarioServicio {

    private final UsuarioRepositorio usuarioRepositorio;
    private final PasswordEncoder passwordEncoder;

    public UsuarioServicio(UsuarioRepositorio usuarioRepositorio, PasswordEncoder passwordEncoder) {
        this.usuarioRepositorio = usuarioRepositorio;
        this.passwordEncoder = passwordEncoder;
    }

    public void registrarUsuario(UsuarioDto usuarioDto) {
        Usuario usuario = new Usuario();
        usuario.setNombreUsuario(usuarioDto.getNombreUsuario());
        usuario.setContraseña(passwordEncoder.encode(usuarioDto.getContraseña()));
        usuarioRepositorio.save(usuario);
    }
}

En este fragmento, se inyecta el PasswordEncoder y se utiliza su método encode para codificar la contraseña proporcionada por el usuario antes de guardarla.

Al momento de la autenticación, Spring Security utiliza el PasswordEncoder configurado para comparar la contraseña ingresada con la almacenada. Esto se realiza de forma automática cuando se utiliza la autenticación estándar con UserDetailsService. No obstante, es crucial asegurarse de que el PasswordEncoder utilizado para codificar la contraseña sea el mismo que se utiliza para verificarla.

La clase BCryptPasswordEncoder permite personalizar el factor de coste, que determina la complejidad del algoritmo y, por ende, el tiempo que toma codificar y verificar una contraseña. Un factor de coste más alto aumenta la seguridad pero también consume más recursos. Por defecto, BCryptPasswordEncoder utiliza un factor de coste de 10, que es adecuado para la mayoría de los casos.

Si se desea ajustar el factor de coste, se puede instanciar BCryptPasswordEncoder especificando el valor deseado:

@Bean
public PasswordEncoder passwordEncoder() {
    int factorDeCoste = 12;
    return new BCryptPasswordEncoder(factorDeCoste);
}

Es importante considerar que todas las contraseñas deben ser codificadas utilizando el mismo factor de coste para evitar inconsistencias en la verificación.

Además de BCryptPasswordEncoder, Spring Security proporciona otras implementaciones de PasswordEncoder, como Pbkdf2PasswordEncoder, SCryptPasswordEncoder o Argon2PasswordEncoder, cada una con sus propias características y niveles de seguridad. No obstante, BCryptPasswordEncoder es ampliamente recomendado debido a su equilibrio entre seguridad y rendimiento.

Para facilitar la gestión de diferentes algoritmos de codificación, se puede utilizar DelegatingPasswordEncoder, que permite delegar la codificación a diferentes encoders basándose en un identificador de prefijo. Por ejemplo:

@Bean
public PasswordEncoder passwordEncoder() {
    String idEnUso = "bcrypt";
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    encoders.put(idEnUso, new BCryptPasswordEncoder());
    encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());

    return new DelegatingPasswordEncoder(idEnUso, encoders);
}

Con este enfoque, las contraseñas almacenadas incluyen un prefijo que indica el algoritmo utilizado, lo que facilita futuras migraciones a algoritmos más seguros sin necesidad de recodificar todas las contraseñas existentes.

Es relevante mencionar que las contraseñas deben ser tratadas como información altamente sensible. Por ello, nunca se deben imprimir en logs, enviar por correo electrónico ni exponer en respuestas HTTP. Además, al manejar contraseñas en formularios, es recomendable utilizar conexiones seguras HTTPS para evitar que sean interceptadas.

En el caso de aplicaciones que permiten a los usuarios cambiar o restablecer sus contraseñas, se debe volver a codificar la nueva contraseña utilizando el PasswordEncoder correspondiente. Por ejemplo:

public void cambiarContraseña(String nombreUsuario, String nuevaContraseña) {
    Usuario usuario = usuarioRepositorio.findByNombreUsuario(nombreUsuario)
        .orElseThrow(() -> new UsernameNotFoundException("Usuario no encontrado"));

    usuario.setContraseña(passwordEncoder.encode(nuevaContraseña));
    usuarioRepositorio.save(usuario);
}

La seguridad de una aplicación es tan fuerte como su eslabón más débil. Por ello, utilizar un PasswordEncoder adecuado como BCryptPasswordEncoder es una práctica esencial para proteger las credenciales de los usuarios y mantener la integridad del sistema. Es parte de una estrategia más amplia que incluye otros aspectos como el control de accesos, la gestión de sesiones y la protección contra vulnerabilidades comunes.

Explicación de SecurityFilterChain y diferencia con WebSecurityConfigurerAdapter

En versiones anteriores de Spring Security, la configuración de la seguridad de la aplicación se realizaba extendiendo la clase WebSecurityConfigurerAdapter

Esta clase proporcionaba métodos para personalizar la autenticación y autorización, permitiendo anular métodos como configure(HttpSecurity http). Sin embargo, a partir de Spring Security 5.7, se recomienda utilizar enfoques basados en componentes y beans, especialmente la definición de un SecurityFilterChain.

El SecurityFilterChain es un bean que configura la cadena de filtros de seguridad aplicados a las solicitudes entrantes. Permite definir cómo se autentican y autorizan las solicitudes, estableciendo reglas y políticas de seguridad específicas para la aplicación. Este enfoque favorece la composición y la inyección de dependencias, alineándose con las prácticas modernas de Spring Framework.

La diferencia fundamental entre SecurityFilterChain y WebSecurityConfigurerAdapter radica en la eliminación de la herencia en favor de la configuración basada en beans. Mientras que WebSecurityConfigurerAdapter requería crear una clase que extendiera dicha clase y anular métodos, el uso de SecurityFilterChain consiste en declarar un bean en una clase de configuración, lo que resulta en una configuración más explícita y modular.

Un ejemplo de configuración utilizando SecurityFilterChain es el siguiente:

@Configuration
@EnableWebSecurity
public class SeguridadConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(autorizaciones -> autorizaciones
                .requestMatchers("/publico/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(config -> config
                .loginPage("/login")
                .permitAll()
            )
            .logout(config -> config
                .permitAll()
            );
        return http.build();
    }
}

En este código, se define un bean SecurityFilterChain que configura el objeto HttpSecurity. Se establecen las reglas de autorización, se personaliza el formulario de inicio de sesión y se habilita el cierre de sesión. Este enfoque evita la necesidad de extender WebSecurityConfigurerAdapter, haciendo la configuración más clara y concisa.

Por el contrario, con el enfoque antiguo, la configuración se veía así:

@EnableWebSecurity
public class SeguridadConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/publico/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }
}

Aquí, la clase SeguridadConfig extiende WebSecurityConfigurerAdapter y anula el método configure, utilizando métodos encadenados para configurar la seguridad. Aunque funcional, este estilo se basa en la herencia y puede dificultar la modularidad y las pruebas unitarias.

La transición al uso de SecurityFilterChain implica varias ventajas:

  • Composición sobre herencia: en lugar de extender una clase padre, se compone la configuración mediante beans y métodos, fomentando una arquitectura más flexible.
  • Facilidad de prueba: los beans pueden ser testeados de forma independiente, lo que mejora la capacidad de realizar pruebas unitarias y de integración.
  • Desacoplamiento: al separar la configuración en componentes más pequeños, se facilita el mantenimiento y la reutilización de la configuración en diferentes contextos.

Además, Spring Security ha introducido mejoras en el uso de lambdas para configurar HttpSecurity, lo que resulta en un código más legible y moderno. Por ejemplo, al configurar la autorización de solicitudes:

http.authorizeHttpRequests(autorizaciones -> autorizaciones
    .requestMatchers("/admin/**").hasRole("ADMIN")
    .requestMatchers("/usuario/**").hasRole("USER")
    .anyRequest().authenticated()
);

Esta sintaxis utiliza expresiones lambda para proporcionar una configuración más clara y concisa, evitando la anidación excesiva de métodos.

Es importante destacar que el cambio hacia SecurityFilterChain también encaja con el enfoque de configuración cero que promueve Spring Boot. Al definir beans que configuran la seguridad, se aprovecha la autoconfiguración y las capacidades de inyección de dependencias del framework.

Para aplicaciones que requieren múltiples cadenas de filtros, es posible definir varios beans de SecurityFilterChain con diferentes órdenes. Por ejemplo:

@Bean
@Order(1)
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/api/**")
        .authorizeHttpRequests(autorizaciones -> autorizaciones
            .anyRequest().hasRole("API_USER")
        )
        .httpBasic();
    return http.build();
}

@Bean
@Order(2)
public SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(autorizaciones -> autorizaciones
            .anyRequest().authenticated()
        )
        .formLogin();
    return http.build();
}

En este ejemplo, se definen dos cadenas de filtros diferentes: una para las rutas que comienzan con /api/ y otra para el resto de las solicitudes. Cada SecurityFilterChain se anota con @Order para especificar el orden en el que deben aplicarse.

La migración desde WebSecurityConfigurerAdapter a SecurityFilterChain es relativamente sencilla. Las configuraciones que se realizaban en el método configure(HttpSecurity http) pueden trasladarse al bean SecurityFilterChain. Es recomendable revisar la documentación oficial y considerar las mejoras en el API de configuración proporcionadas por Spring Security.

Otra ventaja de utilizar SecurityFilterChain es la posibilidad de combinarlo con otras configuraciones personalizadas mediante la inyección de beans adicionales, como AuthenticationProvider, PasswordEncoder y servicios de usuario. Por ejemplo:

@Configuration
public class SeguridadConfig {

    private final UserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;

    public SeguridadConfig(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authenticationProvider(daoAuthenticationProvider())
            .authorizeHttpRequests(autorizaciones -> autorizaciones
                .anyRequest().authenticated()
            )
            .formLogin();
        return http.build();
    }

    @Bean
    public AuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder);
        return provider;
    }
}

En esta configuración, se define un AuthenticationProvider personalizado y se integra en el SecurityFilterChain. Esto demuestra cómo la configuración basada en beans permite una mayor flexibilidad y claridad en la gestión de la seguridad.

Es relevante mencionar que WebSecurityConfigurerAdapter está marcado como obsoleto (deprecated) en las versiones recientes de Spring Security, y es probable que sea eliminado en futuras versiones. Por ello, es aconsejable adoptar el nuevo enfoque con SecurityFilterChain para garantizar la compatibilidad y aprovechar las mejoras del framework.

Aprende SpringBoot GRATIS online

Ejercicios de esta lección Introducción a Spring Security

Evalúa tus conocimientos de esta lección Introducción a Spring Security con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

API Query By Example (QBE)

Spring Boot
Test

Identificadores y relaciones JPA

Spring Boot
Puzzle

Borrar datos de base de datos

Spring Boot
Test

Web y Test Starters

Spring Boot
Puzzle

Métodos find en repositorios

Spring Boot
Test

Controladores Spring MVC

Spring Boot
Código

Inserción de datos

Spring Boot
Test

CRUD Customers Spring MVC + Spring Data JPA

Spring Boot
Proyecto

Backend API REST con Spring Boot

Spring Boot
Proyecto

Controladores Spring REST

Spring Boot
Código

Uso de Spring con Thymeleaf

Spring Boot
Puzzle

API Specification

Spring Boot
Puzzle

Registro de usuarios

Spring Boot
Test

Crear entidades JPA

Spring Boot
Código

Asociaciones en JPA

Spring Boot
Test

Asociaciones de entidades JPA

Spring Boot
Código

Integración con Vue

Spring Boot
Test

Consultas JPQL

Spring Boot
Código

Open API y cómo agregarlo en Spring Boot

Spring Boot
Puzzle

Uso de Controladores REST

Spring Boot
Puzzle

Repositorios reactivos

Spring Boot
Test

Inyección de dependencias

Spring Boot
Test

Introducción a Spring Boot

Spring Boot
Test

CRUD y JPA Repository

Spring Boot
Puzzle

Inyección de dependencias

Spring Boot
Código

Vista en Spring MVC con Thymeleaf

Spring Boot
Test

Servicios en Spring

Spring Boot
Código

Operadores Reactivos

Spring Boot
Puzzle

Configuración de Vue

Spring Boot
Puzzle

Entidades JPA

Spring Boot
Test

Integración con Angular

Spring Boot
Test

API Specification

Spring Boot
Test

API Query By Example (QBE)

Spring Boot
Puzzle

Controladores MVC

Spring Boot
Test

Anotaciones y mapeo en JPA

Spring Boot
Puzzle

Consultas JPQL con @Query en Spring Data JPA

Spring Boot
Test

Repositorios Spring Data

Spring Boot
Test

Inyección de dependencias

Spring Boot
Puzzle

Data JPA y Mail Starters

Spring Boot
Test

Configuración de Angular

Spring Boot
Puzzle

Controladores Spring REST

Spring Boot
Test

Configuración de Controladores MVC

Spring Boot
Puzzle

Consultas JPQL con @Query en Spring Data JPA

Spring Boot
Puzzle

Actualizar datos de base de datos

Spring Boot
Test

Verificar token JWT en peticiones

Spring Boot
Test

Login de usuarios

Spring Boot
Test

Integración con React

Spring Boot
Test

Configuración de React

Spring Boot
Puzzle

Todas las lecciones de SpringBoot

Accede a todas las lecciones de SpringBoot y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Spring Boot

Spring Boot

Introducción Y Entorno

Spring Boot Starters

Spring Boot

Introducción Y Entorno

Inyección De Dependencias

Spring Boot

Introducción Y Entorno

Controladores Spring Mvc

Spring Boot

Spring Web

Vista En Spring Mvc Con Thymeleaf

Spring Boot

Spring Web

Controladores Spring Rest

Spring Boot

Spring Web

Open Api Y Cómo Agregarlo En Spring Boot

Spring Boot

Spring Web

Servicios En Spring

Spring Boot

Spring Web

Clientes Resttemplate Y Restclient

Spring Boot

Spring Web

Rxjava En Spring Web

Spring Boot

Spring Web

Crear Entidades Jpa

Spring Boot

Persistencia Spring Data

Asociaciones De Entidades Jpa

Spring Boot

Persistencia Spring Data

Repositorios Spring Data

Spring Boot

Persistencia Spring Data

Métodos Find En Repositorios

Spring Boot

Persistencia Spring Data

Inserción De Datos

Spring Boot

Persistencia Spring Data

Actualizar Datos De Base De Datos

Spring Boot

Persistencia Spring Data

Borrar Datos De Base De Datos

Spring Boot

Persistencia Spring Data

Consultas Jpql Con @Query En Spring Data Jpa

Spring Boot

Persistencia Spring Data

Api Query By Example (Qbe)

Spring Boot

Persistencia Spring Data

Api Specification

Spring Boot

Persistencia Spring Data

Repositorios Reactivos

Spring Boot

Persistencia Spring Data

Introducción E Instalación De Apache Kafka

Spring Boot

Mensajería Asíncrona

Crear Proyecto Con Apache Kafka

Spring Boot

Mensajería Asíncrona

Creación De Producers

Spring Boot

Mensajería Asíncrona

Creación De Consumers

Spring Boot

Mensajería Asíncrona

Kafka Streams En Spring Boot

Spring Boot

Mensajería Asíncrona

Introducción A Spring Webflux

Spring Boot

Reactividad Webflux

Spring Data R2dbc

Spring Boot

Reactividad Webflux

Controlador Rest Reactivo Basado En Anotaciones

Spring Boot

Reactividad Webflux

Controlador Rest Reactivo Funcional

Spring Boot

Reactividad Webflux

Operadores Reactivos Básicos

Spring Boot

Reactividad Webflux

Operadores Reactivos Avanzados

Spring Boot

Reactividad Webflux

Cliente Reactivo Webclient

Spring Boot

Reactividad Webflux

Introducción A Spring Security

Spring Boot

Seguridad Con Spring Security

Seguridad Basada En Formulario En Mvc Con Thymeleaf

Spring Boot

Seguridad Con Spring Security

Registro De Usuarios

Spring Boot

Seguridad Con Spring Security

Login De Usuarios

Spring Boot

Seguridad Con Spring Security

Verificar Token Jwt En Peticiones

Spring Boot

Seguridad Con Spring Security

Seguridad Jwt En Api Rest Spring Web

Spring Boot

Seguridad Con Spring Security

Seguridad Jwt En Api Rest Reactiva Spring Webflux

Spring Boot

Seguridad Con Spring Security

Autenticación Y Autorización Con Anotaciones

Spring Boot

Seguridad Con Spring Security

Testing Unitario De Componentes Y Servicios

Spring Boot

Testing Con Spring Test

Testing De Repositorios Spring Data Jpa Y Acceso A Datos Con Spring Test

Spring Boot

Testing Con Spring Test

Testing Controladores Spring Mvc Con Thymeleaf

Spring Boot

Testing Con Spring Test

Testing Controladores Rest Con Json

Spring Boot

Testing Con Spring Test

Testing De Aplicaciones Reactivas Webflux

Spring Boot

Testing Con Spring Test

Testing De Seguridad Spring Security

Spring Boot

Testing Con Spring Test

Testing Con Apache Kafka

Spring Boot

Testing Con Spring Test

Integración Con Angular

Spring Boot

Integración Frontend

Integración Con React

Spring Boot

Integración Frontend

Integración Con Vue

Spring Boot

Integración Frontend

Accede GRATIS a SpringBoot y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Conocer qué es Spring Security
  • Conocer las distintas clases dentro de Spring Security
  • Conocer el flujo de autenticación en aplicaciones con Spring Security
  • Aprender a agregar Spring Security a proyectos de Spring Boot