En este tema, aprenderemos cómo implementar la autenticación y autorización de usuarios en una aplicación Spring Boot utilizando Spring Security. La autenticación es el proceso de verificar la identidad de un usuario, mientras que la autorización determina qué recursos puede acceder el usuario autenticado.

Contenido

Introducción a la Autenticación y Autorización

Conceptos Clave

  • Autenticación: Proceso de verificar la identidad de un usuario.
  • Autorización: Proceso de determinar los permisos de un usuario autenticado.
  • Spring Security: Un framework de seguridad para aplicaciones Java que proporciona autenticación y autorización.

Configuración Básica de Spring Security

Para empezar, necesitamos agregar la dependencia de Spring Security en nuestro archivo pom.xml:

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

Spring Boot automáticamente configura Spring Security con una configuración básica. Sin embargo, para personalizar la autenticación y autorización, necesitamos crear una clase de configuración.

Autenticación en Memoria

Configuración de Usuarios en Memoria

Podemos configurar usuarios en memoria para propósitos de prueba. Aquí hay un ejemplo de cómo hacerlo:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user").password("{noop}password").roles("USER")
            .and()
            .withUser("admin").password("{noop}admin").roles("ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/", "/home").permitAll()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }
}

Explicación del Código

  • @EnableWebSecurity: Habilita la seguridad web en la aplicación.
  • configure(AuthenticationManagerBuilder auth): Configura la autenticación en memoria con dos usuarios: user y admin.
  • configure(HttpSecurity http): Configura las reglas de autorización. Solo los usuarios con el rol ADMIN pueden acceder a las rutas /admin/**, y los usuarios con el rol USER pueden acceder a las rutas /user/**.

Autenticación con Base de Datos

Para una aplicación real, es más común autenticar usuarios contra una base de datos. Aquí hay un ejemplo de cómo hacerlo:

Configuración de la Base de Datos

Primero, necesitamos agregar las dependencias de JPA y H2 (o cualquier otra base de datos que prefieras) en el archivo pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

Creación de la Entidad Usuario

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private String role;

    // Getters and Setters
}

Creación del Repositorio de Usuario

import org.springframework.data.jpa.repository.JpaRepository;

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

Configuración de la Autenticación con Base de Datos

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserRepository userRepository;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(username -> {
            User user = userRepository.findByUsername(username);
            if (user == null) {
                throw new UsernameNotFoundException("User not found");
            }
            return org.springframework.security.core.userdetails.User
                    .withUsername(user.getUsername())
                    .password(user.getPassword())
                    .roles(user.getRole())
                    .build();
        }).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/", "/home").permitAll()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

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

Explicación del Código

  • UserRepository: Interfaz que extiende JpaRepository para realizar operaciones CRUD en la entidad User.
  • configure(AuthenticationManagerBuilder auth): Configura la autenticación utilizando el UserRepository para cargar los detalles del usuario desde la base de datos.
  • passwordEncoder(): Define un PasswordEncoder para codificar las contraseñas. En este caso, usamos BCryptPasswordEncoder.

Autorización Basada en Roles

La autorización basada en roles se configura en el método configure(HttpSecurity http) de la clase SecurityConfig. Aquí especificamos qué roles tienen acceso a qué rutas.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").hasRole("USER")
            .antMatchers("/", "/home").permitAll()
            .and()
        .formLogin()
            .loginPage("/login")
            .permitAll()
            .and()
        .logout()
            .permitAll();
}

Explicación del Código

  • antMatchers("/admin/**").hasRole("ADMIN"): Solo los usuarios con el rol ADMIN pueden acceder a las rutas que comienzan con /admin/.
  • antMatchers("/user/**").hasRole("USER"): Solo los usuarios con el rol USER pueden acceder a las rutas que comienzan con /user/.
  • antMatchers("/", "/home").permitAll(): Permite el acceso a todos los usuarios a las rutas / y /home.

Ejercicio Práctico

Ejercicio

  1. Configura una base de datos H2 en tu aplicación Spring Boot.
  2. Crea una entidad User con los campos username, password y role.
  3. Implementa un repositorio UserRepository para realizar operaciones CRUD en la entidad User.
  4. Configura la autenticación y autorización utilizando la base de datos H2.
  5. Crea una página de inicio de sesión personalizada.

Solución

  1. Configuración de la Base de Datos H2:

    spring.datasource.url=jdbc:h2:mem:testdb
    spring.datasource.driverClassName=org.h2.Driver
    spring.datasource.username=sa
    spring.datasource.password=password
    spring.h2.console.enabled=true
    spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
    
  2. Entidad User:

    @Entity
    public class User {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String username;
        private String password;
        private String role;
    
        // Getters and Setters
    }
    
  3. Repositorio UserRepository:

    public interface UserRepository extends JpaRepository<User, Long> {
        User findByUsername(String username);
    }
    
  4. Configuración de Seguridad:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserRepository userRepository;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(username -> {
                User user = userRepository.findByUsername(username);
                if (user == null) {
                    throw new UsernameNotFoundException("User not found");
                }
                return org.springframework.security.core.userdetails.User
                        .withUsername(user.getUsername())
                        .password(user.getPassword())
                        .roles(user.getRole())
                        .build();
            }).passwordEncoder(passwordEncoder());
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .antMatchers("/admin/**").hasRole("ADMIN")
                    .antMatchers("/user/**").hasRole("USER")
                    .antMatchers("/", "/home").permitAll()
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .and()
                .logout()
                    .permitAll();
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    
  5. Página de Inicio de Sesión Personalizada:

    • Crea un archivo login.html en el directorio src/main/resources/templates:
      <!DOCTYPE html>
      <html>
      <head>
          <title>Login</title>
      </head>
      <body>
          <h2>Login</h2>
          <form method="post" action="/login">
              <div>
                  <label>Username:</label>
                  <input type="text" name="username"/>
              </div>
              <div>
                  <label>Password:</label>
                  <input type="password" name="password"/>
              </div>
              <div>
                  <button type="submit">Login</button>
              </div>
          </form>
      </body>
      </html>
      

Conclusión

En esta sección, hemos aprendido cómo implementar la autenticación y autorización de usuarios en una aplicación Spring Boot utilizando Spring Security. Hemos cubierto tanto la autenticación en memoria como la autenticación con base de datos, y hemos visto cómo configurar la autorización basada en roles. Con estos conocimientos, puedes asegurar tu aplicación y controlar el acceso a diferentes partes de tu aplicación según los roles de los usuarios.

En el próximo módulo, exploraremos cómo implementar la autenticación JWT en Spring Boot para una seguridad más avanzada y escalable.

Curso de Spring Boot

Módulo 1: Introducción a Spring Boot

Módulo 2: Conceptos Básicos de Spring Boot

Módulo 3: Construyendo Servicios Web RESTful

Módulo 4: Acceso a Datos con Spring Boot

Módulo 5: Seguridad en Spring Boot

Módulo 6: Pruebas en Spring Boot

Módulo 7: Funciones Avanzadas de Spring Boot

Módulo 8: Despliegue de Aplicaciones Spring Boot

Módulo 9: Rendimiento y Monitoreo

Módulo 10: Mejores Prácticas y Consejos

© Copyright 2024. Todos los derechos reservados