Các Giải Pháp Khắc Phục Lỗi CORS Trong Ứng Dụng Spring Boot

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 CorsConfigurationSourceSecurityFilterChain (đố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 @CrossOrigin trê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ặc allowedOrigins("*"). 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ụng allowedOrigins("*"). Bạn phải sử dụng allowedOriginPatterns("*") hoặc liệt kê cụ thể các nguồn gốc cho phép.

Thẻ: Spring Boot CORS WebMvcConfigurer Spring Security Cross-Origin Resource Sharing

Đăng vào ngày 21 tháng 5 lúc 21:09