Giới Thiệu Về Lỗi CORS và Chính Sách Cùng Nguồn Gốc
Lỗi CORS (Cross-Origin Resource Sharing) là một cơ chế bảo mật quan trọng do trình duyệt web áp dụng, dựa trên chính sách cùng nguồn gốc (Same-Origin Policy). Vấn đề này phát sinh khi một yêu cầu HTTP được gửi từ một nguồn gốc (origin) khác với nguồn gốc của tài nguyên mà nó muốn truy cập. "Nguồn gốc" được định nghĩa bởi sự kết hợp của giao thức (protocol), tên miền (domain) và cổng (port).
Ví dụ, nếu phía client (ví dụ: một ứng dụng web chạy trên http://localhost:3000) cố gắng gửi yêu cầu tới một API server (ví dụ: http://localhost:8080), trình duyệt sẽ tự động chặn phản hồi từ server vì hai nguồn gốc này không trùng khớp (khác cổng). Điều này ngăn chặn việc đánh cắp dữ liệu hoặc các cuộc tấn công bảo mật khác giữa các ứng dụng chạy trên các nguồn gốc khác nhau.
Các Phương Pháp Giải Quyết Lỗi CORS Trong Spring Boot
1. Sử Dụng Annotation @CrossOrigin (Cấu Hình Cục Bộ)
Phương pháp này cho phép bạn định nghĩa các quy tắc CORS trực tiếp tại cấp độ Controller hoặc từng phương thức xử lý yêu cầu. Đây là lựa chọn tốt khi bạn cần kiểm soát CORS một cách chi tiết cho các endpoint cụ thể.
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/products")
public class ProductController {
// Cho phép bất kỳ nguồn gốc nào truy cập phương thức này
@CrossOrigin(origins = "*")
@GetMapping("/list")
public String getAllProducts() {
return "Danh sách sản phẩm";
}
// Chỉ cho phép nguồn gốc cụ thể và phương thức POST, PUT
@CrossOrigin(origins = {"http://localhost:4200", "http://myfrontend.com"}, methods = {RequestMethod.POST, RequestMethod.PUT})
@PostMapping("/add")
public String addNewProduct(@RequestBody String productDetails) {
return "Thêm sản phẩm mới thành công: " + productDetails;
}
}
2. Cấu Hình CORS Toàn Cục (Khuyến Nghị)
Đây là phương pháp được khuyến nghị cho hầu hết các ứng dụng Spring Boot, đặc biệt là các dự án phân tách frontend/backend. Bằng cách triển khai interface WebMvcConfigurer và ghi đè phương thức addCorsMappings, bạn có thể định nghĩa các quy tắc CORS áp dụng cho toàn bộ ứng dụng.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/api/**") // Áp dụng cho tất cả các API dưới /api/
.allowedOrigins("http://localhost:3000", "http://localhost:4200", "http://myfrontend.com") // Chỉ định các nguồn gốc được phép
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // Các phương thức HTTP được phép
.allowedHeaders("*") // Cho phép tất cả các header trong yêu cầu
.allowCredentials(true) // Cho phép gửi cookie, header ủy quyền, v.v.
.maxAge(3600); // Thời gian sống của kết quả pre-flight request (giây)
}
}
3. Cấu Hình Sử Dụng Filter
Bạn cũng có thể xử lý CORS bằng cách tạo một Filter tùy chỉnh. Phương pháp này cung cấp mức độ kiểm soát thấp hơn so với WebMvcConfigurer nhưng vẫn là một lựa chọn hợp lệ, đặc biệt trong các trường hợp cần tích hợp sâu hơn vào chuỗi filter của servlet.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CustomCorsFilterConfiguration {
@Bean
public CorsFilter corsFilterBean() {
UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsSettings = new CorsConfiguration();
// Cấu hình các thiết lập CORS
corsSettings.addAllowedOriginPattern("*"); // Cho phép bất kỳ nguồn gốc nào
corsSettings.addAllowedMethod("*"); // Cho phép tất cả các phương thức HTTP (GET, POST, PUT, DELETE...)
corsSettings.addAllowedHeader("*"); // Cho phép tất cả các header
corsSettings.setAllowCredentials(true); // Cho phép gửi thông tin xác thực (cookies, HTTP authentication)
configurationSource.registerCorsConfiguration("/**", corsSettings); // Áp dụng cấu hình cho tất cả các đường dẫn
return new CorsFilter(configurationSource);
}
}
4. Tích Hợp Với Spring Security
Nếu dự án của bạn sử dụng Spring Security, việc cấu hình CORS cần được thực hiện trong chuỗi bảo mật. Spring Security cung cấp các tiện ích để dễ dàng tích hợp CORS thông qua CorsConfigurationSource và SecurityFilterChain (đối với Spring Security 5.7+ trở lên).
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.Collections;
@Configuration
@EnableWebSecurity
public class SecuritySetup {
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors(cors -> cors.configurationSource(corsConfigurationSource())) // Kích hoạt CORS và sử dụng cấu hình đã định nghĩa
.csrf(csrf -> csrf.disable()) // Tắt CSRF nếu không cần thiết cho API stateless
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll() // Cho phép tất cả các yêu cầu (tùy chỉnh theo yêu cầu bảo mật)
);
return httpSecurity.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration settings = new CorsConfiguration();
settings.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://anotherclient.com")); // Nguồn gốc được phép
settings.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // Phương thức HTTP được phép
settings.setAllowedHeaders(Collections.singletonList("*")); // Cho phép tất cả header
settings.setAllowCredentials(true); // Cho phép thông tin xác thực
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", settings); // Áp dụng cho tất cả các đường dẫn
return configSource;
}
}
5. Thiết Lập Header Phản Hồi Thủ Công
Đây là phương pháp ít được khuyến khích nhất và chỉ nên dùng cho mục đích kiểm tra hoặc trong các trường hợp đặc biệt. Bạn có thể tự thêm các header CORS vào phản hồi HTTP trong từng endpoint.
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SystemInfoController {
@GetMapping("/status")
public ResponseEntity<String> getSystemStatus() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Access-Control-Allow-Origin", "http://localhost:3000"); // Chỉ định nguồn gốc cụ thể
responseHeaders.add("Access-Control-Allow-Methods", "GET"); // Chỉ định phương thức
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type"); // Chỉ định header
return new ResponseEntity<>("Hệ thống đang hoạt động tốt!", responseHeaders, HttpStatus.OK);
}
}
Tóm Tắt & Lưu Ý Quan Trọng
Trong số các giải pháp đã trình bày, cấu hình CORS toàn cục thông qua WebMvcConfigurer (Giải pháp 2) là phương pháp được ưu tiên và sử dụng rộng rãi nhất. Nó cung cấp sự cân bằng tốt giữa tính linh hoạt, dễ quản lý và hiệu quả, đặc biệt phù hợp cho các ứng dụng kiến trúc microservices hoặc SPA (Single Page Application) với backend Spring Boot riêng biệt.
Ưu điểm của cấu hình toàn cục:
- Dễ triển khai: Tập trung quản lý tất cả các quy tắc CORS tại một nơi duy nhất.
- Tính linh hoạt cao: Cho phép tùy chỉnh chi tiết các nguồn gốc, phương thức HTTP, header, và thông tin xác thực.
- Hiệu quả cho dự án lớn: Tránh lặp lại code
@CrossOrigintrên nhiều Controller hoặc phương thức.
Các lưu ý quan trọng khi triển khai:
- Môi trường sản phẩm: Trong môi trường production, KHÔNG BAO GIỜ sử dụng
allowedOriginPatterns("*")hoặcallowedOrigins("*"). Thay vào đó, hãy chỉ định rõ ràng các tên miền frontend hợp lệ (ví dụ:.allowedOrigins("https://your-frontend.com")) để đảm bảo an toàn. - Kết hợp với Spring Security: Nếu dự án của bạn sử dụng Spring Security, hãy đảm bảo rằng CORS được cấu hình chính xác trong chuỗi filter bảo mật, như đã trình bày trong Giải pháp 4.
- Sử dụng
allowCredentials(true): Khi bạn muốn cho phép gửi thông tin xác thực (như cookie hoặc HTTP authentication), bạn không thể sử dụngallowedOrigins("*"). Bạn phải sử dụngallowedOriginPatterns("*")hoặc liệt kê cụ thể các nguồn gốc cho phép.