Nguyên nhân là thứ tự filter: preflight OPTIONS của trình duyệt không mang credential (không cookie, không header Authorization). Nếu filter chain của Spring Security chạy trước phần xử lý CORS, nó thấy request không xác thực và trả 401 ngay — chưa bao giờ tới được @CrossOrigin ở tầng controller, nên trình duyệt báo lỗi CORS.
Cách sửa đúng: bật CORS bên trong Spring Security để CorsFilter được đặt trước các filter xác thực.
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.cors(Customizer.withDefaults()) // registers CorsFilter early in the chain
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}Hai lưu ý khi phỏng vấn hỏi sâu:
- allowCredentials(true) không đi kèm allowedOrigins("") — spec cấm; nếu cần wildcard thì dùng setAllowedOriginPatterns.
- Cách "mở OPTIONS cho tất cả" bằng requestMatchers(HttpMethod.OPTIONS, "/*").permitAll() chạy được nhưng thô hơn: nó bỏ qua toàn bộ kiểm tra cho method OPTIONS thay vì để CorsFilter trả đúng header.