2. Xác thực API bằng JWT
Trong phần này, chúng ta sẽ tìm hiểu cách xác thực API trong ứng dụng Java Spring Boot bằng JSON Web Token (JWT). JWT là một tiêu chuẩn mở (RFC 7519) cho phép truyền tải thông tin an toàn giữa các bên dưới dạng đối tượng JSON. Nó thường được sử dụng để xác thực người dùng trong các ứng dụng web và di động.
Thêm phụ thuộc vào dự án
Để sử dụng JWT trong Spring Boot, bạn cần thêm các phụ thuộc sau vào tệp build.gradle (sử dụng Gradle):
// Thêm vào build.gradle
// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
Yêu cầu trước khi làm việc với JWT
Để sử dụng JWT, bạn cần có một lớp người dùng (User) với các thuộc tính như username, password, và các quyền (roles) của người dùng. Bạn cũng
cần một lớp UserJpaRepository để truy xuất thông tin người dùng từ cơ sở dữ liệu. Dưới đây là một ví dụ về lớp người dùng và repository:
Lớp người dùng (User)
Để đại diện cho người dùng trong hệ thống, bạn cần tạo một lớp User với các thuộc tính như id, username, password, và danh sách các quyền (roles) của người dùng. Dưới đây là một ví dụ về lớp này:
package com.example.demo.entities;
import java.util.List;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "Users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
private List<Role> roles;
}
Lớp quyền (Role)
Để quản lý quyền của người dùng, bạn cần tạo một lớp Role để đại diện cho các quyền khác nhau trong hệ thống. Dưới đây là một ví dụ về lớp này:
package com.example.demo.entities;
import java.util.List;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private List<User> users;
}
Tạo lớp UserJpaRepository
Để truy xuất thông tin người dùng từ cơ sở dữ liệu, bạn cần tạo một lớp UserJpaRepository sử dụng Spring Data JPA. Lớp này sẽ cung cấp các phương thức để tìm kiếm người dùng theo tên đăng nhập (username). Dưới đây là một ví dụ về cách tạo lớp này:
package com.example.demo.repositories;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import com.example.demo.entities.User;
@Repository
public interface UserJpaRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.username = :username")
Optional<User> findByUsername(String username);
}
Tạo lớp UserService
Để xử lý logic xác thực người dùng, bạn cần tạo một lớp UserService. Lớp này sẽ sử dụng UserJpaRepository để tìm kiếm người dùng và xác thực thông tin đăng nhập. Dưới đây là một ví dụ về cách tạo lớp này:
package com.example.demo.services;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import com.example.demo.dtos.LoginRequestDto;
import com.example.demo.dtos.LoginResponseDto;
import com.example.demo.entities.User;
import com.example.demo.exceptions.HttpException;
import com.example.demo.repositories.UserJpaRepository;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class UserService {
private final JwtService jwtService;
private final UserJpaRepository userJpaRepository;
public LoginResponseDto login(LoginRequestDto request) throws Exception {
// Find the user by email (username)
User user = this.userJpaRepository.findByUsername(request.getUsername())
.orElseThrow(() -> new HttpException("Invalid username or password", HttpStatus.UNAUTHORIZED));
// Verify password
if (!request.getPassword().equals(user.getPassword())) {
throw new HttpException("Invalid username or password", HttpStatus.UNAUTHORIZED);
}
// Generate a new access token (with full data + roles)
String accessToken = jwtService.generateAccessToken(user);
return LoginResponseDto.builder()
.id(user.getId())
.username(user.getUsername())
.accessToken(accessToken)
.build();
}
}
Tạo lớp CustomUserDetailsService
Để sử dụng JWT, bạn cần tạo một lớp CustomUserDetailsService để tải thông tin người dùng từ cơ sở dữ liệu. Lớp này sẽ triển khai UserDetailsService và sử dụng UserJpaRepository để truy xuất thông tin người dùng. Dưới đây là một ví dụ về cách tạo lớp này:
package com.example.demo.services;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.example.demo.entities.User;
import com.example.demo.repositories.UserJpaRepository;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserJpaRepository userRepository;
public CustomUserDetailsService(UserJpaRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
List<GrantedAuthority> authorities = new ArrayList<>();
user.getRoles().forEach(role -> {
// Nếu dùng @PreAuthorize("hasAuthority('Administrators')") thì
authorities.add(new SimpleGrantedAuthority(role.getName()));
// Nếu dùng @PreAuthorize("hasRole('Administrators')") thì authorities.add(new
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
});
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(authorities)
.build();
}
}