Mục Tiêu
- Tối ưu hiệu năng bằng việc chỉ lấy token khi cần thiết.
- Sử dụng Method Security và HttpSecurity để thiết lập phân quyền truy cập.
- Phân quyền dựa trên vai trò (roles).
- Kiểm soát phiên làm việc (session) từ phía máy chủ và quản lý đăng xuất từ backend.
- Dễ dàng tích hợp với nhiều phương thức xác thực khác nhau, mang lại trải nghiệm người dùng đơn giản và dễ sử dụng.
Kiến Thức Cần Thiết
Vòng Đời Của Một Request Trong Spring
- Client gửi một request lên Server. Request này sẽ đi qua chuỗi các Filter đã được cài đặt trong Spring Boot, thực hiện xác thực và kiểm tra quyền truy cập trước khi đến Controller.
- Request được xử lý bởi Controller, nơi thực hiện các nghiệp vụ cần thiết như truy xuất dữ liệu từ service hoặc repository, và tạo ra Response.
- Sau khi Controller hoàn tất, Response chứa kết quả xử lý sẽ được tạo ra và gửi ngược qua các Filter mà Request đã đi qua.
- Cuối cùng, Response sẽ được trả về cho Client để xử lý.
Cách Spring Security Sử Dụng Filter Để Kiểm Tra Phân Quyền
- Spring Security sử dụng Filter để quản lý quyền truy cập của người dùng. Ví dụ, khi sử dụng Session, một cookie được tạo ra để theo dõi phiên làm việc của Client.
- Nếu cookie JWT bị xóa, người dùng sẽ bị đăng xuất ngay lập tức.
- Khi trang web được truy cập, Spring Security quản lý trạng thái đăng nhập thông qua SessionManagementFilter.
Tích Hợp JWT
Khi người dùng đăng nhập thành công, thay vì sử dụng JSESSIONID, Server sẽ gửi JWT token dưới dạng Cookie. Điều này giúp kiểm soát session từ phía máy chủ và cho phép chức năng đăng xuất thông qua backend.
Thiết Lập Dự Án
Cấu Trúc Thư Mục
.
├── mvnw
├── mvnw.cmd
├── pom.xml
├── springsecurityjwt
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── huyvu
│ │ │ └── springsecurityjwt
│ │ │ ├── SpringsecurityjwtApplication.java
│ │ │ ├── controller
│ │ │ │ └── HomeController.java
│ │ │ └── security
│ │ │ ├── JwtTokenVo.java
│ │ │ ├── LazySecurityContextProviderFilter.java
│ │ │ ├── SecurityConfig.java
│ │ │ └── SecurityUtils.java
│ │ └── resources
│ │ └── application.properties
│ └── test
│ └── java
│ └── com
│ └── huyvu
│ └── springsecurityjwt
│ └── SpringsecurityjwtApplicationTests.java
Thư Viện Cần Có
- Java 17
- Spring Boot 3.x
- Spring Security 6.x
- com.auth0.java-jwt-4.4.0
pom.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.huyvu</groupId>
<artifactId>springsecurityjwt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecurityjwt</name>
<description>springsecurityjwt</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
SecurityUtils.java
java
package com.huyvu.springsecurityjwt.security;
// Các import và annotations cần thiết
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SecurityUtils {
// Các hằng số và định nghĩa cần thiết
public static String createToken(JwtTokenVo jwtTokenVo) {
// Tạo JWT token
}
public static void setJwtToClient(JwtTokenVo vo){
// Ghi Token vào Cookie
}
public static DecodedJWT validate(String token) {
// Giải mã JWT token
}
public static JwtTokenVo getValueObject(DecodedJWT decodedJWT) {
// Lấy thông tin từ JWT claims
}
public static String getToken(HttpServletRequest req) {
// Lấy Token từ Cookie
}
public static JwtTokenVo getSession(){
// Lấy thông tin đăng nhập hiện tại
}
}
- Lớp này giúp tạo, giải mã, đọc Token từ Request và ghi Token vào Response, sử dụng trong các lớp Business.
JwtTokenVo.java
java
package com.huyvu.springsecurityjwt.security;
// Các import và annotations cần thiết
@Data
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = PRIVATE)
public class JwtTokenVo {
Long uId;
String username;
List<String> roles;
// Phương thức trả về danh sách quyền
}
LazySecurityContextProviderFilter.java
java
package com.huyvu.springsecurityjwt.security;
// Các import và annotations cần thiết
@Slf4j
@Component
@RequiredArgsConstructor
public class LazySecurityContextProviderFilter extends OncePerRequestFilter {
// Xử lý logic JWT
}
- Filter này xử lý logic khi cần xác thực thông tin từ Request, tối ưu hiệu suất của ứng dụng.
SecurityConfig.java
java
package com.huyvu.springsecurityjwt.security;
// Các import và annotations cần thiết
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, LazySecurityContextProviderFilter lazySecurityContextProviderFilter) throws Exception {
// Thiết lập HttpSecurity
}
}
HomeController.java
java
package com.huyvu.springsecurityjwt.controller;
// Các import và annotations cần thiết
@RestController
public class HomeController {
// Các trường hợp test cho các endpoint
}
Kiểm Tra
Trạng Thái Chưa Đăng Nhập
Người dùng chưa có JWT Cookie sẽ bị từ chối khi truy cập.
Đăng Nhập Với Quyền Admin, User, Guest
- Khi đăng nhập thành công, hệ thống gửi Cookie có tên
Authorization
cho trình duyệt. - Người dùng chỉ có thể truy cập các endpoint tương ứng với quyền của mình.
Nguồn Tài Liệu
Câu Hỏi Thường Gặp
- Tại sao cần sử dụng Lazy Security Context Provider?
- Tại sao phải parse JwtTokenVo thành JSON trước khi thêm vào JWT claims?
- Tại sao cần exclude UserDetailsServiceAutoConfig?
- Tại sao đặt LazySecurityContextProviderFilter trước SessionManagementFilter?
source: viblo