JavaRush /Курсы /Модуль 5. Spring /Хранение пользователей и ролей в базе данных

Хранение пользователей и ролей в базе данных

Модуль 5. Spring
10 уровень , 6 лекция
Открыта

В реальных приложениях данные о пользователях и их ролях (а также разрешениях) редко хранятся в статических конфигурационных файлах. База данных делает управление пользователями более гибким и удобным. Например:

  1. Динамическое управление пользователями — возможность добавления и удаления пользователей "на лету".
  2. Безопасность — пароли пользователей могут быть надёжно зашифрованы прямо в базе данных.
  3. Масштабируемость — поддержка большого количества пользователей.
  4. Интеграция — легко синхронизировать пользователей из других систем (например, CRM).

Что будем делать

А вот что:

  1. Создадим таблицы для хранения данных пользователей и ролей.
  2. Настроим связь между таблицами (много ко многим).
  3. Создадим сущности пользователя (User) и роли (Role).
  4. Реализуем работу с базой данных через репозитории Spring Data JPA.
  5. Настроим UserDetailsService для интеграции базы данных с Spring Security.

Шаг 1. Создание таблиц для пользователей и ролей

Начнём с дизайна базы данных. Нам нужны две таблицы: одна для хранения информации о пользователях, другая — для ролей. Помимо этого, необходимо связать их через промежуточную таблицу, чтобы реализовать связь "многие ко многим". Вот пример SQL-запроса для создания таблиц:


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)
);

Вот что значат эти поля:

  • Таблица user:
    • username — имя пользователя, используется для входа в систему.
    • password — храним зашифрованный пароль (подробнее об этом позже).
    • enabled — флаг, обозначающий, активен ли пользователь.
  • Таблица role:
    • Каждая роль содержит уникальное имя (например, ROLE_USER, ROLE_ADMIN).
  • Таблица user_roles:
    • Представляет связь между пользователями и их ролями.

Шаг 2. Создание сущностей User и Role

Сначала создадим Java-классы, представляющие наши таблицы. Мы будем использовать JPA-аннотации для сопоставления полей сущностей с колонками в таблицах базы данных.

Класс 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;
    }
}

Класс 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;
    }
}

Шаг 3. Репозитории Spring Data JPA

Теперь создадим репозитории для взаимодействия с базой данных.

Интерфейс RoleRepository


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

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

Интерфейс UserRepository


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

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

Эти репозитории упростят выполнение часто используемых операций: поиск пользователя по имени, добавление новых ролей и т.д.


Шаг 4. Настройка UserDetailsService

Для интеграции базы данных с Spring Security нужно предоставить реализацию интерфейса UserDetailsService. Это позволит фреймворку загружать информацию о пользователе во время аутентификации.

Реализация 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();
    }
}

Шаг 5. Добавление шифрования паролей

Шифрование паролей — обязательный этап. Используем 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();
    }
}

Когда создаёте новых пользователей в базе данных, обязательно шифруйте их пароли через PasswordEncoder.


Результаты

Теперь ваше приложение готово к безопасной работе с пользователями и ролями из базы данных. Вы можете добавлять, обновлять и управлять ними, не внося изменения в код. Такой подход часто используется в корпоративных системах, интернет-магазинах и любой другой системе, которая требует гибкости в управлении пользователями. Не забудьте напомнить пользователям придумать действительно сложные пароли, чтобы они не оказались жертвами password123!

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ