跨域资源共享(CORS)是现代Web开发中常见的问题,Spring Boot提供了多种方式来处理CORS请求。下面我将详细介绍各种实现方式及其适用场景。
一、CORS基础概念
1. 什么是跨域请求?
当浏览器从一个域名的网页去请求另一个域名的资源时,如果域名、端口或协议不同,就会产生跨域请求。出于安全考虑,浏览器默认会阻止这类请求。
2. 简单请求 vs 预检请求
类型 | 条件 | 处理方式 |
---|---|---|
简单请求 | GET/HEAD/POST方法,且Content-Type为text/plain、multipart/form-data或application/x-www-form-urlencoded | 直接发送请求,带Origin头 |
预检请求(OPTIONS) | 不符合简单请求条件的其他请求 | 先发送OPTIONS请求,获得许可后再发送实际请求 |
二、Spring Boot处理CORS的5种方式
1. 使用@CrossOrigin注解
适用场景:针对单个控制器或方法级别的CORS配置
@RestController
@RequestMapping("/api")
public class MyController {
// 允许特定源的跨域访问
@CrossOrigin(origins = "https://example.com")
@GetMapping("/resource")
public ResponseEntity<String> getResource() {
return ResponseEntity.ok("跨域资源");
}
// 更详细的配置
@CrossOrigin(origins = {"https://example.com", "https://api.example.com"},
allowedHeaders = {"Content-Type", "Authorization"},
methods = {RequestMethod.GET, RequestMethod.POST},
maxAge = 3600)
@PostMapping("/save")
public ResponseEntity<String> saveResource() {
return ResponseEntity.ok("保存成功");
}
}
2. 全局CORS配置
适用场景:应用级别的统一CORS配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 匹配的路径
.allowedOrigins("https://example.com", "https://api.example.com") // 允许的源
.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的方法
.allowedHeaders("*") // 允许的请求头
.exposedHeaders("Authorization", "Content-Disposition") // 暴露的响应头
.allowCredentials(true) // 是否允许发送cookie
.maxAge(3600); // 预检请求缓存时间(秒)
// 可以添加多个配置
registry.addMapping("/public/**")
.allowedOrigins("*");
}
}
3. 使用Filter处理CORS
适用场景:需要更底层控制或与非Spring Web环境集成
@Configuration
public class CorsFilterConfig {
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// 配置CORS规则
config.setAllowCredentials(true);
config.addAllowedOrigin("https://example.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(3600L);
// 对所有路径生效
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean =
new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 设置最高优先级
return bean;
}
}
4. Spring Security中的CORS配置
适用场景:使用Spring Security的项目
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and() // 启用CORS支持
.csrf().disable() // 通常CORS和CSRF不能同时使用
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated();
}
// 提供CORS配置源
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
5. 响应头手动设置
适用场景:需要动态控制CORS头
@RestController
public class DynamicCorsController {
@GetMapping("/dynamic-cors")
public ResponseEntity<String> dynamicCors(HttpServletRequest request,
HttpServletResponse response) {
// 根据请求动态设置CORS头
String origin = request.getHeader("Origin");
if (isAllowedOrigin(origin)) {
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST");
}
return ResponseEntity.ok("动态CORS响应");
}
private boolean isAllowedOrigin(String origin) {
// 实现你的源验证逻辑
return origin != null && origin.endsWith("example.com");
}
}
三、CORS配置详解
1. 核心响应头说明
响应头 | 说明 |
---|---|
Access-Control-Allow-Origin | 允许访问的源,可以是具体域名或*(不推荐使用*,特别是需要凭证时) |
Access-Control-Allow-Methods | 允许的HTTP方法(GET, POST等) |
Access-Control-Allow-Headers | 允许的请求头 |
Access-Control-Expose-Headers | 浏览器可以访问的响应头 |
Access-Control-Allow-Credentials | 是否允许发送cookie(true/false),设为true时Allow-Origin不能为* |
Access-Control-Max-Age | 预检请求结果的缓存时间(秒) |
2. 常见问题解决方案
问题1:预检请求(OPTIONS)被拦截
解决方案:
- 确保OPTIONS请求不被安全框架拦截
- 在Spring Security中配置:
http.cors().and() .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
问题2:带凭证的请求失败
解决方案:
- 确保
allowCredentials(true)
和具体的allowedOrigins
(不能是*) - 前端需要设置
withCredentials: true
(如axios)
问题3:特定响应头无法获取
解决方案:
- 使用
exposedHeaders
暴露需要的头:.exposedHeaders("Custom-Header", "Authorization")
四、最佳实践建议
- 生产环境不要使用通配符*:明确指定允许的源
- 合理限制HTTP方法:只开放必要的方法(GET/POST等)
- 考虑使用环境变量:动态配置允许的源
@Value("${cors.allowed.origins}") private String[] allowedOrigins; // 在配置中使用 .allowedOrigins(allowedOrigins)
- 结合安全框架:Spring Security项目使用专门的CORS配置
- 测试不同场景:简单请求和预检请求都要测试
五、完整配置示例
@Configuration
@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {
@Value("${app.cors.allowed-origins}")
private String[] allowedOrigins;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(allowedOrigins)
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("Authorization", "Content-Disposition")
.allowCredentials(true)
.maxAge(3600);
registry.addMapping("/public/**")
.allowedOrigins("*")
.allowedMethods("GET", "OPTIONS");
}
// 可选:提供CORS过滤器作为备选
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList(allowedOrigins));
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
六、总结
Spring Boot提供了多种灵活的方式来处理CORS:
- 简单场景:使用
@CrossOrigin
注解 - 统一配置:实现
WebMvcConfigurer
的addCorsMappings
方法 - 底层控制:配置
CorsFilter
- 安全项目:结合Spring Security的
cors()
配置 - 动态需求:手动设置响应头
根据项目需求选择合适的方式,并遵循安全最佳实践,可以有效地解决跨域问题,同时保证应用的安全性。