CodeGym /Cursos /Módulo 5. Spring /Almacenamiento de usuarios y roles en la base de datos

Almacenamiento de usuarios y roles en la base de datos

Módulo 5. Spring
Nivel 17 , Lección 6
Disponible

En aplicaciones reales, los datos sobre los usuarios y sus roles (y también los permisos) rara vez se guardan en ficheros de configuración estáticos. Una base de datos hace la gestión de usuarios más flexible y cómoda. Por ejemplo:

  1. Gestión dinámica de usuarios — posibilidad de añadir y eliminar usuarios "al vuelo".
  2. Seguridad — las contraseñas de los usuarios pueden cifrarse de forma segura directamente en la base de datos.
  3. Escalabilidad — soporte para un gran número de usuarios.
  4. Integración — fácil sincronizar usuarios de otros sistemas (por ejemplo, CRM).

Qué haremos

Y esto es lo que haremos:

  1. Crearemos tablas para almacenar los datos de usuarios y roles.
  2. Configuraremos la relación entre las tablas (muchos a muchos).
  3. Crearemos las entidades de usuario (User) y role (Role).
  4. Implementaremos el acceso a la base de datos mediante repositorios Spring Data JPA.
  5. Configuraremos UserDetailsService para integrar la base de datos con Spring Security.

Paso 1. Creación de las tablas para usuarios y roles

Empecemos por el diseño de la base de datos. Necesitamos dos tablas: una para la información de los usuarios y otra para los roles. Además, hay que enlazarlas mediante una tabla intermedia para implementar la relación "muchos a muchos". Aquí tienes un ejemplo de consulta SQL para crear las tablas:


CREATE TABLE role (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE
);

CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    enabled BOOLEAN NOT NULL DEFAULT TRUE
);

CREATE TABLE user_roles (
    user_id BIGINT,
    role_id BIGINT,
    PRIMARY KEY (user_id, role_id),
    CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES user(id),
    CONSTRAINT fk_role FOREIGN KEY (role_id) REFERENCES role(id)
);

Esto es lo que significan estos campos:

  • Tabla user:
    • username — nombre de usuario, se usa para el login.
    • password — guardamos la contraseña cifrada (más abajo hablamos de esto).
    • enabled — flag que indica si el usuario está activo.
  • Tabla role:
    • Cada role tiene un nombre único (por ejemplo, ROLE_USER, ROLE_ADMIN).
  • Tabla user_roles:
    • Representa la relación entre los usuarios y sus roles.

Paso 2. Creación de las entidades User y Role

Primero crearemos las clases Java que representan nuestras tablas. Usaremos anotaciones JPA para mapear los campos de las entidades con las columnas de la base de datos.

Clase Role


import jakarta.persistence.*;
import java.util.Set;

@Entity
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    public Role() {}

    public Role(String name) {
        this.name = name;
    }

    // Getters and Setters

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Clase User


import jakarta.persistence.*;
import java.util.Set;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Column(nullable = false)
    private String password;

    private boolean enabled = true;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles;

    public User() {}

    public User(String username, String password, Set<Role> roles) {
        this.username = username;
        this.password = password;
        this.roles = roles;
    }

    // Getters and Setters

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

Paso 3. Repositorios Spring Data JPA

Ahora crearemos los repositorios para interactuar con la base de datos.

Interfaz RoleRepository


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

public interface RoleRepository extends JpaRepository<Role, Long> {
    Role findByName(String name);
}

Interfaz UserRepository


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

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

Estos repositorios simplifican operaciones comunes: buscar un usuario por nombre, añadir nuevas roles, etc.


Paso 4. Configuración de UserDetailsService

Para integrar la base de datos con Spring Security hay que proporcionar una implementación de la interfaz UserDetailsService. Eso permitirá al framework cargar la información del usuario durante la autenticación.

Implementación de UserDetailsService


import org.springframework.security.core.userdetails.*;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Service;

import java.util.stream.Collectors;

@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 {
        User user = userRepository.findByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }

        return User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .authorities(user.getRoles().stream()
                        .map(Role::getName)
                        .collect(Collectors.toList()))
                .build();
    }
}

Paso 5. Añadir cifrado de contraseñas

Cifrar las contraseñas es imprescindible. Usaremos BCryptPasswordEncoder:


import org.springframework.context.annotation.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

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

Cuando crees nuevos usuarios en la base de datos, asegúrate de cifrar sus contraseñas con PasswordEncoder.


Resultados

Ahora tu aplicación está lista para trabajar de forma segura con usuarios y roles desde la base de datos. Puedes añadir, actualizar y gestionarlos sin tocar el código. Este enfoque se usa mucho en sistemas corporativos, tiendas online y cualquier otro sistema que necesite flexibilidad en la gestión de usuarios. No olvides recordar a los usuarios que elijan contraseñas realmente fuertes para que no caigan en la trampa de password123!

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION