Spring Security + JPA – Xác thực từ cơ sở dữ liệu

🧩 Bài 2: Spring Security + JPA – Xác thực từ cơ sở dữ liệu

🎯 Mục tiêu

  • Tạo bảng users trong MySQL.
  • Sử dụng Spring Data JPA để truy xuất thông tin người dùng.
  • Tích hợp Spring Security để xác thực người dùng từ database.

1️⃣ Chuẩn bị cơ sở dữ liệu

Tạo CSDL:

CREATE DATABASE demo_security;

Tạo bảng users:

CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
role VARCHAR(50) NOT NULL
);

Thêm người dùng thử nghiệm:

INSERT INTO users (username, password, role)
VALUES ('admin', '{noop}123456', 'ROLE_ADMIN');

⚠️ {noop} dùng để chỉ mật khẩu chưa mã hóa (chỉ dùng demo).


2️⃣ Cấu hình application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/demo_security
spring.datasource.username=root
spring.datasource.password=your_password

spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true

3️⃣ Tạo Entity User

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

private String username;
private String password;
private String role;

// Getters & setters
}

4️⃣ Repository truy xuất người dùng

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

5️⃣ Custom UserDetails và UserDetailsService

🔸 UserDetailsImpl:

public class UserDetailsImpl implements UserDetails {
private final User user;

public UserDetailsImpl(User user) {
this.user = user;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority(user.getRole()));
}

@Override
public String getPassword() { return user.getPassword(); }

@Override
public String getUsername() { return user.getUsername(); }

@Override
public boolean isAccountNonExpired() { return true; }

@Override
public boolean isAccountNonLocked() { return true; }

@Override
public boolean isCredentialsNonExpired() { return true; }

@Override
public boolean isEnabled() { return true; }
}

🔸 UserDetailsServiceImpl:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepo;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepo.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return new UserDetailsImpl(user);
}
}

6️⃣ Cấu hình bảo mật

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Autowired
private UserDetailsServiceImpl userDetailsService;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public").permitAll()
.anyRequest().authenticated())
.httpBasic();

return http.build();
}

@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.passwordEncoder(NoOpPasswordEncoder.getInstance()) // chỉ dùng demo
.and().build();
}
}

7️⃣ Test thử endpoint

@RestController
public class DemoController {

@GetMapping("/public")
public String publicAPI() {
return "Trang công khai!";
}

@GetMapping("/private")
public String privateAPI() {
return "Bạn đã đăng nhập thành công!";
}
}

✅ Kết luận

Bạn vừa xây dựng hệ thống xác thực người dùng bằng Spring Security và dữ liệu trong MySQL thông qua Spring Data JPA. Từ đây, bạn có thể:

  • Bổ sung mã hóa password bằng BCrypt
  • Xây dựng trang đăng nhập tùy chỉnh (login form)
  • Phân quyền nâng cao theo ROLE

🧠 Vì sao cần UserDetails trong Spring Security?

Khi Spring Security thực hiện xác thực người dùng, framework cần 1 mô hình chuẩn để:

  • Lấy được usernamepassword,
  • Biết được người dùng có quyền hạn (roles/authorities) gì,
  • Kiểm tra trạng thái của tài khoản (có bị khóa, hết hạn, vô hiệu hóa… không).

➡️ Vì vậy, Spring Security định nghĩa interface UserDetails như là khuôn mẫu chuẩn.

public interface UserDetails {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}

🛠 Vai trò trong hệ thống

  • Khi người dùng đăng nhập (POST /login hoặc gọi API bảo vệ),
  • Spring Security sẽ gọi loadUserByUsername(username) trong UserDetailsService.
  • UserDetailsService sẽ trả về một đối tượng UserDetails chứa toàn bộ thông tin cần thiết về người dùng.

Spring sẽ dùng các thông tin đó để:

  • Kiểm tra mật khẩu.
  • Kiểm tra tài khoản có bị khóa / vô hiệu hóa / hết hạn không.
  • Gán các quyền cho người dùng (authorities).

💡 Vì sao không trả thẳng Entity User?

  • User của bạn (Entity JPA) có thể chứa rất nhiều thông tin riêng, ví dụ như email, số điện thoại, ngày sinh, ảnh đại diện,…
  • Trong khi đó, xác thực chỉ cần một số thông tin nhỏ (username, password, roles, trạng thái tài khoản).
  • Để tách biệt trách nhiệm và giữ cho quy trình xác thực bảo mật và rõ ràng, Spring yêu cầu một đối tượng chuyên biệt là UserDetails.

👉 Vậy nên, bạn tạo thêm lớp UserDetailsImpl để chuyển đổi từ Entity User thành mô hình chuẩn UserDetails.


🏆 Tóm tắt ngắn gọn

Entity UserUserDetails
Chứa toàn bộ thông tin người dùng trong databaseChứa thông tin tối thiểu cần thiết để xác thực
Quản lý bởi JPAQuản lý bởi Spring Security
Dùng cho lưu trữ, xử lý dữ liệu nghiệp vụDùng cho quy trình xác thực (Authentication)

🛡️ Quy trình Xác thực trong Spring Security với UserDetails

[User gửi thông tin đăng nhập (username, password)]
|
v
[Spring Security nhận yêu cầu login]
|
v
[Gọi UserDetailsService.loadUserByUsername(username)]
|
v
[Tải UserDetails từ database]
- getUsername()
- getPassword()
- getAuthorities()
- isAccountNonLocked()
- isEnabled()
|
v
[Spring Security kiểm tra thông tin]
- So khớp password
- Kiểm tra trạng thái tài khoản
|
v
[Xác thực thành công ✅] ---> [Gán quyền vào SecurityContextHolder]
|
v
[Tiếp tục truy cập các API được bảo vệ]

🎯 Các thành phần chính tham gia:

Thành phầnVai trò
AuthenticationManagerQuản lý xác thực
UsernamePasswordAuthenticationTokenGói username + password
UserDetailsServiceLoad thông tin người dùng từ database
UserDetailsĐối tượng chứa thông tin xác thực chuẩn hóa
SecurityContextHolderLưu trữ thông tin người dùng sau xác thực thành công

📚 Cách kết nối các thành phần

  • UsernamePasswordAuthenticationFilter sẽ lấy username, password từ HTTP request.
  • Gửi đến AuthenticationManager.
  • AuthenticationManager gọi UserDetailsService.loadUserByUsername().
  • UserDetailsService tìm trong CSDL → trả về UserDetails.
  • So sánh password, kiểm tra account status.
  • Nếu đúng, lưu thông tin xác thực vào SecurityContextHolder.

🧠 Một câu dễ nhớ

UserDetails là “hộ chiếu” giúp người dùng vượt qua cổng kiểm tra an ninh (Spring Security).

Comments are closed.