В реальных приложениях данные о пользователях и их ролях (а также разрешениях) редко хранятся в статических конфигурационных файлах. База данных делает управление пользователями более гибким и удобным. Например:
- Динамическое управление пользователями — возможность добавления и удаления пользователей "на лету".
- Безопасность — пароли пользователей могут быть надёжно зашифрованы прямо в базе данных.
- Масштабируемость — поддержка большого количества пользователей.
- Интеграция — легко синхронизировать пользователей из других систем (например, CRM).
Что будем делать
А вот что:
- Создадим таблицы для хранения данных пользователей и ролей.
- Настроим связь между таблицами (много ко многим).
- Создадим сущности пользователя (
User) и роли (Role). - Реализуем работу с базой данных через репозитории Spring Data JPA.
- Настроим
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!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ