CodeWalk

CORS跨域问题与Spring Security配置

作者:苦行僧 · 2026-05-30 12:55

请详细解释**CORS(Cross-Origin Resource Sharing)**跨域资源共享机制。什么是跨域?浏览器同源策略是什么?简单请求和预检请求(Preflight)的区别?如何在Spring Boot / Spring Security中正确配置CORS(@CrossOrigin / WebMvcConfigurer / CorsFilter)?

回答

苦行僧

同源策略与跨域

同源:协议 + 域名 + 端口 三者完全相同。

跨域:不同源之间的请求被浏览器限制(默认只允许同源)。

简单请求 vs 预检请求

简单请求(无需预检):

  • 方法:GET / HEAD / POST
  • 头:Accept / Accept-Language / Content-Language / Content-Type(限text/plain、multipart/form-data、application/x-www-form-urlencoded)

预检请求(Preflight)

  • 浏览器先发OPTIONS请求,携带:
    • Origin: https://a.com
    • Access-Control-Request-Method: PUT
    • Access-Control-Request-Headers: Authorization
  • 服务端响应:
    • Access-Control-Allow-Origin: https://a.com
    • Access-Control-Allow-Methods: PUT, POST
    • Access-Control-Allow-Headers: Authorization
    • Access-Control-Max-Age: 3600
  • 预检通过后,浏览器才发真实请求

Spring Boot CORS配置

1. @CrossOrigin(局部)

@RestController
@CrossOrigin(origins = "http://localhost:3000", allowedHeaders = "*")
public class UserController {
    @GetMapping("/user")
    @CrossOrigin(origins = "http://admin.example.com")  // 可覆盖类级别
    public User getUser() { ... }
}

2. WebMvcConfigurer(全局)

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://frontend.com", "http://localhost:3000")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedHeaders("Authorization", "Content-Type")
            .exposedHeaders("X-Total-Count")    // 暴露给前端的响应头
            .allowCredentials(true)              // 允许携带Cookie
            .maxAge(3600);                       // 预检缓存时间
    }
}

3. Spring Security + CORS

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors(Customizer.withDefaults())   // 启用CORS(使用CorsConfigurationSource)
            .csrf(csrf -> csrf.disable())      // API通常禁用CSRF
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            );
        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("http://localhost:3000"));
        config.setAllowedMethods(List.of("*"));
        config.setAllowedHeaders(List.of("*"));
        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}

注意事项

  • allowCredentials(true)时,allowedOrigins不能为"*",需指定具体域名
  • 生产环境不要开放所有域名
  • 代理服务器(Nginx)也可解决跨域