Spring Boot

SpringBoot

Tutorial SpringBoot: Seguridad basada en formulario en MVC con Thymeleaf

Programar seguridad con Spring Security basada en formulario login y logout personalizado en Spring Web en controladores MVC con Spring Boot y Thymeleaf.

Aprende SpringBoot GRATIS y certifícate

Configuración entidades

Para gestionar la autenticación y autorización en nuestra aplicación de Spring Boot 3, es esencial configurar las entidades User y Role con una relación ManyToMany. Además, estableceremos la entidad BlogPost (o Producto) con un campo author que referencia a un User utilizando Lombok para simplificar nuestro código.

Comenzamos definiendo la entidad Role, que representará los roles de usuario dentro del sistema, como "ADMIN" o "USER". Utilizamos las anotaciones de JPA para mapear la entidad a una tabla en la base de datos y Lombok para generar automáticamente los métodos getters, setters y constructores necesarios:

@Entity
@Table(name = "roles")
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Role {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();
}

 La relación ManyToMany con la entidad User se establece con @ManyToMany(mappedBy = "roles"), indicando que el propietario de la relación es la entidad User.

La entidad User se define de la siguiente manera:

@Entity
@Table(name = "users")
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @ToString.Exclude
    @ManyToMany
    @JoinTable(
        name = "users_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles = new HashSet<>();
}

Aquí, la relación ManyToMany se configura explícitamente con @JoinTable, especificando la tabla intermedia users_roles y las columnas de unión correspondientes. El uso de Set<Role> en lugar de List<Role> evita duplicados y mejora el rendimiento en ciertas operaciones. La anotación @Column con unique = true en username asegura que no existan usuarios duplicados en la base de datos.

Para asociar un User como autor de un BlogPost, definimos la entidad BlogPost con una relación ManyToOne hacia User:

@Entity
@Table(name = "blog_posts")
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class BlogPost {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @Lob
    private String content;

    @ManyToOne(optional = false)
    @JoinColumn(name = "author_id")
    private User author;
}

La anotación @ManyToOne(optional = false) indica que cada BlogPost debe tener obligatoriamente un author asociado. El campo @Lob en content permite almacenar textos de gran tamaño, ideal para el contenido del blog. Usando Lombok, mantenemos el código conciso y legible, sin necesidad de escribir manualmente los métodos básicos.

Es importante manejar correctamente las relaciones bidireccionales para evitar problemas de rendimiento y referencialidad. Por ejemplo, en la entidad User, podemos agregar un conjunto de BlogPosts para reflejar la relación inversa:

@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<BlogPost> blogPosts = new HashSet<>();

De esta forma, podemos acceder fácilmente a los BlogPosts de un usuario y mantener la integridad de los datos. La opción cascade = CascadeType.ALL asegura que las operaciones realizadas sobre el usuario se propaguen a sus BlogPosts, y orphanRemoval = true elimina los BlogPosts huérfanos cuando un usuario es eliminado.

Al utilizar Lombok, también podemos crear constructores personalizados si necesitamos inicializar ciertos campos de manera específica. Por ejemplo:

@RequiredArgsConstructor
public class User {
    // campos...

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
}

La anotación @RequiredArgsConstructor genera un constructor con los atributos marcados como final o con restricciones como @NonNull. Esto nos ayuda a garantizar que ciertos campos importantes nunca sean nulos, mejorando la consistencia de la entidad.

Para completar la configuración, es recomendable implementar la interfaz UserDetails en nuestra entidad User para integrarla con Spring Security:

@Entity
@Table(name = "users")
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User implements UserDetails {
    // campos...

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream()
            .map(role -> new SimpleGrantedAuthority(role.getName()))
            .collect(Collectors.toSet());
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    // Otros métodos de UserDetails...
}

Al implementar los métodos de UserDetails, proporcionamos a Spring Security la información requerida para manejar la autenticación y la autorización de usuarios en el sistema. Los roles se transforman en instancias de GrantedAuthority, que son utilizados por Spring Security para controlar el acceso a los recursos.

Finalmente, es crucial realizar la persistencia de estas entidades utilizando repositorios de Spring Data JPA. Por ejemplo, podemos crear un repositorio para la entidad User:

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

Este repositorio nos permite buscar usuarios por su nombre de usuario, lo cual es esencial durante el proceso de autenticación. La utilización de Optional es una buena práctica para manejar la posible ausencia de resultados y evitar NullPointerExceptions.

Implementar UserDetailsService con Spring Data JPA

Para integrar nuestras entidades User y Role con Spring Security, es necesario implementar la interfaz UserDetailsService. Esta interfaz proporciona un método para cargar información específica del usuario durante el proceso de autenticación.

Comenzamos creando una clase que implemente UserDetailsService, donde utilizaremos nuestro UserRepository para obtener los usuarios desde la base de datos:

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("Usuario no encontrado: " + username));
    }
}

Esta clase está anotada con @Service para que Spring la detecte como un componente gestionado. El constructor inyecta el UserRepository utilizando inyección de dependencias. En el método loadUserByUsername, buscamos el usuario por su nombre de usuario y, si no se encuentra, lanzamos una excepción UsernameNotFoundException.

Es importante que nuestra entidad User implemente la interfaz UserDetails para que pueda ser utilizada por Spring Security. Ya hemos implementado UserDetails en la entidad User previamente, incluyendo los métodos necesarios como getAuthorities(), getPassword() y getUsername(). Esto permite que la autenticación utilice directamente nuestra entidad User.

Necesitemos definir un PasswordEncoder para manejar el cifrado de las contraseñas:

@Configuration
public class SecurityConfig {

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

Configuramos dentro de SecurityConfig el SecurityFilterChain:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authorize -> authorize
            .anyRequest().authenticated()
        )
        .formLogin(form -> form
            .loginPage("/login")
            .permitAll()
        );

    return http.build();
}

En esta configuración, indicamos que todas las solicitudes requieren autenticación y especificamos que usaremos un formulario de inicio de sesión personalizado en /login

Es recomendable inicializar algunos usuarios y roles en nuestra base de datos para probar el sistema de autenticación. Podemos hacerlo creando una clase de inicialización de datos:

@Component
public class DataInitializer implements CommandLineRunner {

    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    private final PasswordEncoder passwordEncoder;

    public DataInitializer(UserRepository userRepository, RoleRepository roleRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.roleRepository = roleRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void run(String... args) {
        Role roleUser = new Role();
        roleUser.setName("ROLE_USER");
        roleRepository.save(roleUser);

        Role roleAdmin = new Role();
        roleAdmin.setName("ROLE_ADMIN");
        roleRepository.save(roleAdmin);

        User user = new User();
        user.setUsername("usuario");
        user.setPassword(passwordEncoder.encode("contraseña"));
        user.getRoles().add(roleUser);
        userRepository.save(user);

        User admin = new User();
        admin.setUsername("administrador");
        admin.setPassword(passwordEncoder.encode("contraseña"));
        admin.getRoles().add(roleUser);
        admin.getRoles().add(roleAdmin);
        userRepository.save(admin);
    }
}

En esta clase, anotada con @Component, utilizamos el método run de CommandLineRunner para insertar datos iniciales. Creamos roles y usuarios, asignamos roles a los usuarios y codificamos las contraseñas con passwordEncoder.

Es esencial verificar que las contraseñas se almacenen codificadas en la base de datos. De esta manera, garantizamos la seguridad de las credenciales y cumplimos con buenas prácticas de desarrollo.

Con todo configurado, al iniciar la aplicación, podremos acceder a la página de inicio de sesión personalizada y autenticarnos con los usuarios creados. Spring Security utilizará nuestra implementación de UserDetailsService para cargar los detalles del usuario y verificar las credenciales.

Si necesitamos personalizar aún más la autenticación, podemos implementar métodos adicionales en nuestra clase CustomUserDetailsService o ajustar la configuración en SecurityConfig. Por ejemplo, podríamos manejar usuarios bloqueados, expiración de cuentas o añadir campos adicionales de autenticación.

Configuración  de SecurityFilterChain

 En esta sección, configuraremos la seguridad global basada en formulario, permitiendo que los usuarios se autentiquen a través de una página de inicio de sesión personalizada.

El siguiente paso es definir el SecurityFilterChain en profundidad, donde configuraremos las reglas de seguridad y especificaremos cómo se manejarán las peticiones HTTP:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authorize -> authorize
            .requestMatchers("/login", "/css/**", "/js/**").permitAll()
            .anyRequest().authenticated()
        )
        .formLogin(form -> form
            .loginPage("/login")
            .defaultSuccessUrl("/home", true)
            .permitAll()
        )
        .logout(logout -> logout
            .logoutUrl("/logout")
            .logoutSuccessUrl("/login?logout")
            .permitAll()
        );

    return http.build();
}

En esta configuración, hemos definido varios aspectos clave:

  • Autorizaciones de solicitudes: Permitimos el acceso sin autenticación a la página de /login y a los recursos estáticos como /css/ y /js/. Todas las demás solicitudes requieren autenticación.
  • Inicio de sesión basado en formulario: Especificamos que la página de inicio de sesión personalizada se encuentra en /login, y definimos la URL de redirección tras un inicio de sesión exitoso con defaultSuccessUrl("/home", true).
  • Cierre de sesión: Configuramos la URL para cerrar la sesión en /logout y definimos la redirección tras un cierre de sesión exitoso.

Es importante destacar que hemos utilizado la API fluida de HttpSecurity para establecer nuestras configuraciones de manera clara y legible.

Con esta configuración, nuestra aplicación ya está protegida por Spring Security, y los usuarios deberán autenticarse para acceder a los recursos protegidos. Los intentos de acceso sin autenticación a rutas protegidas redirigirán al usuario a la página de inicio de sesión.

Es posible que también necesitemos configurar excepciones para permitir el acceso a recursos como imágenes o fuentes. Podemos ajustar el método authorizeHttpRequests para incluir estas rutas:

.requestMatchers("/images/**", "/webjars/**").permitAll()

Asimismo, si estamos utilizando Thymeleaf, es recomendable configurar el manejo de recursos estáticos correctamente.

Para mejorar la experiencia del usuario, podemos personalizar los mensajes de error en el inicio de sesión, manejando casos como credenciales incorrectas o cuentas bloqueadas. Esto se logra agregando parámetros en la configuración de formLogin:

.failureUrl("/login?error=true")

De esta manera, redirigiremos al usuario nuevamente a la página de inicio de sesión en caso de error, y podremos mostrar mensajes adecuados.

También es posible configurar la gestión de sesiones para controlar el comportamiento de las sesiones de usuario:

.sessionManagement(session -> session
    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
    .invalidSessionUrl("/login?invalid-session=true")
    .maximumSessions(1)
    .maxSessionsPreventsLogin(true)
)

Con estas opciones, limitamos a un solo inicio de sesión por usuario y manejamos sesiones inválidas apropiadamente.

Vistas login y logout personalizadas con Thymeleaf

Una vez configurada la seguridad en nuestra aplicación Spring Boot 3, es fundamental ofrecer una experiencia de usuario coherente mediante vistas personalizadas de login y logout utilizando Thymeleaf. Esto permite mantener la identidad visual de la aplicación y proporcionar mensajes y elementos adaptados a nuestras necesidades.

Para crear una vista de login personalizada, comenzamos por diseñar una plantilla Thymeleaf que represente el formulario de inicio de sesión. Creamos el archivo login.html dentro de la carpeta templates:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Iniciar Sesión</title>
    <link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
    <h1>Por favor, inicia sesión</h1>
    <form th:action="@{/login}" method="post">
        <div>
            <label for="username">Usuario:</label>
            <input type="text" id="username" name="username" autofocus>
        </div>
        <div>
            <label for="password">Contraseña:</label>
            <input type="password" id="password" name="password">
        </div>
        <div>
            <input type="checkbox" id="remember-me" name="remember-me">
            <label for="remember-me">Recordarme</label>
        </div>
        <div>
            <button type="submit">Entrar</button>
        </div>
        <div th:if="${param.error}">
            <p class="error">Nombre de usuario o contraseña incorrectos</p>
        </div>
        <div th:if="${param.logout}">
            <p class="message">Has cerrado sesión correctamente</p>
        </div>
    </form>
</body>
</html>

En esta plantilla, utilizamos la expresión Thymeleaf th:action="@{/login}" para indicar que el formulario enviará los datos a la ruta /login. Esto es coherente con la configuración de seguridad que maneja la autenticación. Es importante incluir los campos username y password, ya que Spring Security los espera con esos nombres por defecto.

Además, gestionamos los mensajes de error y de cierre de sesión exitoso mediante las condiciones th:if="${param.error}" y th:if="${param.logout}". Estos parámetros son añadidos automáticamente por Spring Security cuando ocurre un error de autenticación o cuando el usuario cierra sesión.

Para estilizar el formulario y mejorar la usabilidad, podemos enlazar una hoja de estilos CSS personalizada a través de th:href="@{/css/style.css}".

El siguiente paso es asegurarnos de que nuestra configuración de seguridad reconoce esta plantilla como la página de inicio de sesión. En la clase SecurityConfig, debemos haber especificado la ruta de la página de login personalizada:

.formLogin(form -> form
    .loginPage("/login")
    .defaultSuccessUrl("/home", true)
    .permitAll()
)

Con esta configuración, Spring Security redirige a los usuarios no autenticados a /login cuando intentan acceder a recursos protegidos. Además, permitAll() permite el acceso sin autenticación a la página de login.

Es posible que necesitemos crear un controlador que maneje la solicitud GET a /login. Aunque Spring Security maneja las solicitudes POST del formulario de login automáticamente, la ruta GET para mostrar la página de login debe ser manejada por un controlador:

@Controller
public class LoginController {

    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

Este controlador sencillo devuelve la vista login.html cuando se accede a /login mediante una solicitud GET. De esta manera, los usuarios pueden ver el formulario de inicio de sesión personalizado.

Si deseamos personalizar aún más el proceso de autenticación, como cambiar los nombres de los campos del formulario o la URL de procesamiento del login, podemos configurar estos detalles en formLogin:

.formLogin(form -> form
    .loginPage("/login")
    .loginProcessingUrl("/perform_login")
    .usernameParameter("user")
    .passwordParameter("pass")
    .defaultSuccessUrl("/home", true)
    .permitAll()
)

En este caso, debemos ajustar el formulario en login.html para que los campos tengan los nuevos nombres:

<input type="text" id="user" name="user" autofocus>
<input type="password" id="pass" name="pass">
<form th:action="@{/perform_login}" method="post">

Sin embargo, es recomendable utilizar los nombres por defecto (username y password) para simplificar la configuración.

Para implementar una vista personalizada de logout, podemos añadir un enlace en el menú o en alguna parte de la aplicación donde el usuario autenticado pueda cerrar sesión:

<a th:href="@{/logout}">Cerrar Sesión</a>

Por defecto, Spring Security maneja las solicitudes POST a /logout para cerrar sesión. Si deseamos que el cierre de sesión se realice mediante una solicitud GET (por ejemplo, al hacer clic en un enlace), debemos ajustar la configuración de seguridad:

.logout(logout -> logout
    .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
    .logoutSuccessUrl("/login?logout")
    .permitAll()
)

Sin embargo, por motivos de seguridad, se recomienda utilizar solicitudes POST para acciones sensibles como el cierre de sesión. Una alternativa es utilizar JavaScript para enviar una solicitud POST cuando el usuario hace clic en el enlace.

Si queremos mostrar una página personalizada tras el cierre de sesión, podemos crear una vista logout.html y ajustar la configuración:

.logout(logout -> logout
    .logoutSuccessUrl("/logout-success")
)

Y en el controlador:

@Controller
public class LogoutController {

    @GetMapping("/logout-success")
    public String logoutSuccess() {
        return "logout-success";
    }
}

Además, en nuestra plantilla de Thymeleaf, podemos mostrar el nombre del usuario autenticado utilizando la expresión #authentication:

<p>Bienvenido, <span th:text="${#authentication.name}"></span></p>

De este modo, mejoramos la experiencia de usuario al mostrar información relevante y dinámica.

Control de acceso en vistas de thymeleaf

Después de haber configurado la seguridad en nuestra aplicación y personalizado las vistas de login y logout, es importante manejar el control de acceso en nuestras vistas de Thymeleaf. Esto nos permite mostrar u ocultar elementos en las páginas dependiendo de los roles o privilegios del usuario autenticado. Además, es necesario asignar el usuario autenticado como autor o responsable al crear nuevas entidades, como es el caso de la entidad Producto.

Para controlar el acceso en las vistas, Thymeleaf proporciona un dialecto de seguridad que facilita este proceso. Primero, asegurémonos de que el dialecto de Spring Security esté incluido en nuestra aplicación. Normalmente, esto se logra mediante la dependencia correspondiente en el archivo build.gradle o pom.xml:

<!-- Dependencia para Maven -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>

Una vez incluido el dialecto, es opcional registrarlo en nuestra configuración de Thymeleaf. Si estamos utilizando Spring Boot, esto suele ser automático. Sin embargo, si contamos con una configuración manual, debemos añadir el dialecto:

@Bean
public SpringTemplateEngine templateEngine() {
    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
    templateEngine.setTemplateResolver(templateResolver());
    templateEngine.addDialect(new SpringSecurityDialect());
    return templateEngine;
}

Con el dialecto de Spring Security habilitado, podemos utilizar las etiquetas de seguridad en nuestras vistas de Thymeleaf. Por ejemplo, si queremos mostrar un enlace solamente a usuarios con el rol ADMIN, podemos hacerlo de la siguiente manera:

<nav>
    <ul>
        <li><a th:href="@{/}" >Inicio</a></li>
        <li sec:authorize="hasRole('ADMIN')">
            <a th:href="@{/productos/nuevo}" >Crear Producto</a>
        </li>
    </ul>
</nav>

Aquí, la etiqueta sec:authorize verifica si el usuario tiene el rol ADMIN antes de renderizar el elemento HTML correspondiente. De esta forma, los usuarios sin dicho rol no verán el enlace para crear un nuevo producto, mejorando la seguridad y la experiencia del usuario.

Además de controlar la visualización de elementos, también podemos acceder a la información del usuario autenticado en las vistas. Por ejemplo, para mostrar el nombre de usuario en la página:

<div>
    <p>Bienvenido, <span sec:authentication="name"></span></p>
</div>

La expresión sec:authentication="name" permite obtener el nombre de usuario desde el contexto de autenticación. Esto es útil para personalizar el contenido y ofrecer una interacción más dinámica.

Al crear una nueva entidad Producto, es fundamental asignar el usuario autenticado como creador o autor del producto. Para lograr esto en el controlador, necesitamos acceder al usuario actual. En Spring MVC, podemos obtener el usuario autenticado mediante el objeto Principal o utilizando la anotación @AuthenticationPrincipal.

Por ejemplo, en el controlador de productos:

@PostMapping("/productos")
public String guardarProducto(@ModelAttribute Producto producto, @AuthenticationPrincipal User usuarioAutenticado) {
    producto.setAutor(usuarioAutenticado);
    productoServicio.guardar(producto);
    return "redirect:/productos";
}

En este código, el parámetro @AuthenticationPrincipal User usuarioAutenticado inyecta el usuario actualmente autenticado. Asumimos que la clase User es nuestra entidad de usuario que implementa UserDetails. A continuación, asignamos el usuario como autor del producto antes de guardarlo.

Es importante asegurarse de que la entidad Producto tiene una relación adecuada con la entidad User. Por ejemplo, en la clase Producto:

@Entity
public class Producto {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nombre;
    private BigDecimal precio;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "autor_id")
    private User autor;

    // Getters y setters
}

La anotación @ManyToOne establece la relación con el usuario creador del producto. De esta manera, cada producto está asociado a un usuario, lo que permite controlar permisos y realizar auditorías.

En algunos casos, es posible que necesitemos acceder al usuario autenticado dentro de servicios u otros componentes. Para ello, podemos utilizar la clase SecurityContextHolder de Spring Security:

User usuarioAutenticado = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

Sin embargo, es recomendable utilizar la inyección directa en el controlador mediante @AuthenticationPrincipal para mantener el código más limpio y legible.

En las vistas, podemos comprobar si el usuario tiene ciertos roles o permisos y ajustar la interfaz en consecuencia. Por ejemplo, para mostrar opciones de edición solo al autor o a administradores:

<div sec:authorize="hasRole('ADMIN') or principal.username == ${producto.autor.username}">
    <a th:href="@{'/productos/editar/' + ${producto.id}}">Editar</a>
    <a th:href="@{'/productos/eliminar/' + ${producto.id}}" onclick="return confirm('¿Estás seguro?');">Eliminar</a>
</div>

La expresión principal.username nos permite acceder al nombre de usuario autenticado y compararlo con el autor del producto. De esta forma, solo el autor del producto o un administrador puede ver los enlaces de edición y eliminación.

Ejemplo de usos de etiquetas thymeleaf para seguridad en barra de navegación navbar con bootstrap 5:

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
                xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>

    <nav th:fragment="nav1" class="navbar navbar-expand-lg bg-light mb-5">
        <div class="container-fluid">
            <a class="navbar-brand" href="/foods">
                <img class="img-fluid nav-logo" src="/img/bookstore.png" alt="Logo">
            </a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse"
                    data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
                    aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item">
                        <a class="nav-link" th:href="@{/foods}">Foods</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" th:href="@{/supermarkets}">Supermarkets</a>
                    </li>
                </ul>


                <div class="d-flex" sec:authorize="isAnonymous()">
                    <a class="btn btn-link" th:href="@{/login}">Iniciar sesión</a>
                </div>
                <div class="d-flex" sec:authorize="isAnonymous()">
                    <a class="btn btn-link" th:href="@{/user/registration}">Registro</a>
                </div>
                <div class="d-flex" sec:authorize="isAuthenticated()">
                    <span sec:authentication="principal.username"></span>
                </div>
                <div class="d-flex" sec:authorize="isAuthenticated()">
                    <a class="btn btn-link" th:href="@{/logout}">Cerrar sesión</a>
                </div>
            </div>
        </div>
    </nav>


</body>
</html>

Es importante tener en cuenta que el control de acceso en las vistas es una medida adicional y no sustituye a la seguridad en el lado del servidor. Siempre debemos validar los permisos en el backend para evitar accesos no autorizados manipulando las solicitudes.

Para finalizar, recordemos que la gestión de permisos y roles es esencial para mantener una aplicación segura y funcional. Al combinar las etiquetas de seguridad en Thymeleaf con las anotaciones en controladores y el manejo adecuado del usuario autenticado, podemos implementar un control de acceso robusto y eficiente en nuestra aplicación Spring Boot.

Aprende SpringBoot GRATIS online

Ejercicios de esta lección Seguridad basada en formulario en MVC con Thymeleaf

Evalúa tus conocimientos de esta lección Seguridad basada en formulario en MVC con Thymeleaf 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

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

  • Incorporar Spring Security en aplicaciones MVC
  • Manejar la seguridad en backend
  • Manejar la seguridad en vistas HTML de Thymeleaf
  • Control de acceso y detección del usuario autenticado