一、CORS基础概念
1.1 什么是跨域请求?
跨域资源共享(Cross-Origin Resource Sharing, CORS)是一种安全机制,它允许Web应用程序在一个域上的资源请求另一个域上的资源。浏览器出于安全考虑,会阻止不同源之间的AJAX请求,这是**同源策略(Same-Origin Policy)**的限制。
1.2 同源策略定义
两个URL被认为是同源的条件:
- 协议相同(http/https)
- 域名相同
- 端口相同
例如:
http://example.com/app1
和http://example.com/app2
→ 同源http://example.com
和https://example.com
→ 不同源(协议不同)http://example.com
和http://api.example.com
→ 不同源(域名不同)http://example.com
和http://example.com:8080
→ 不同源(端口不同)
二、Spring Boot处理CORS的5种方式
2.1 全局配置(推荐)
方式1:使用WebMvcConfigurer
接口
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 所有接口
.allowedOrigins("*") // 允许所有源
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许方法
.allowedHeaders("*") // 允许所有头
.allowCredentials(true) // 允许凭证
.maxAge(3600); // 预检请求缓存时间
}
}
方式2:使用Filter
方式(适用于Servlet应用)
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
2.2 控制器方法级配置
方式3:使用@CrossOrigin
注解
@RestController
@RequestMapping("/api")
public class UserController {
// 单个方法配置
@CrossOrigin(origins = "http://localhost:3000")
@GetMapping("/users")
public List<User> getUsers() {
// ...
}
// 整个控制器配置
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/products")
public class ProductController {
// ...
}
}
2.3 属性文件配置(Spring Boot 2.4+)
方式4:application.yml
配置
spring:
mvc:
cors:
allowed-origins: "http://localhost:3000, https://example.com"
allowed-methods: "GET, POST, PUT, DELETE"
allowed-headers: "*"
exposed-headers: "Authorization, Content-Disposition"
allow-credentials: true
max-age: 1800
等效的application.properties
:
spring.mvc.cors.allowed-origins=http://localhost:3000, https://example.com
spring.mvc.cors.allowed-methods=GET, POST, PUT, DELETE
spring.mvc.cors.allowed-headers=*
spring.mvc.cors.exposed-headers=Authorization, Content-Disposition
spring.mvc.cors.allow-credentials=true
spring.mvc.cors.max-age=1800
2.4 响应头手动设置
方式5:手动添加响应头(灵活但繁琐)
@RestController
public class ApiController {
@GetMapping("/manual")
public ResponseEntity<String> manualCors() {
return ResponseEntity.ok()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET")
.body("Manual CORS configured");
}
}
三、CORS处理深度解析
3.1 预检请求(Preflight Request)
对于"非简单请求",浏览器会先发送OPTIONS预检请求:
简单请求条件:
- 使用GET、HEAD或POST方法
- 仅包含以下头:
- Accept
- Accept-Language
- Content-Language
- Content-Type (仅限 application/x-www-form-urlencoded, multipart/form-data, text/plain)
非简单请求示例:
fetch('http://api.example.com/data', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
},
body: JSON.stringify({
key: 'value'})
});
Spring Boot会自动处理OPTIONS请求,无需开发者额外编码。
3.2 核心响应头说明
响应头 | 说明 |
---|---|
Access-Control-Allow-Origin | 允许访问的源,* 表示任何源 |
Access-Control-Allow-Methods | 允许的HTTP方法 |
Access-Control-Allow-Headers | 允许的请求头 |
Access-Control-Expose-Headers | 允许浏览器访问的响应头 |
Access-Control-Allow-Credentials | 是否允许发送Cookie和HTTP认证信息 |
Access-Control-Max-Age | 预检请求结果的缓存时间(秒) |
3.3 常见问题解决方案
问题1:allowCredentials(true)
与allowedOrigins("*")
冲突
错误:
When allowCredentials is true, allowedOrigins cannot contain the special value "*"
解决方案:
// 替换为具体域名
.allowedOrigins("http://localhost:3000", "https://example.com")
问题2:前端仍然报CORS错误
检查步骤:
- 确保后端已正确配置
- 检查浏览器控制台错误详情
- 使用Postman等工具验证接口是否正常工作
- 检查是否有多个CORS配置相互覆盖
问题3:自定义过滤器干扰CORS
解决方案:
确保CorsFilter在过滤器链中的优先级:
@Bean
public FilterRegistrationBean<CorsFilter> corsFilterRegistration() {
FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(corsFilter());
registration.setOrder(Ordered.HIGHEST_PRECEDENCE); // 最高优先级
return registration;
}
四、安全最佳实践
4.1 生产环境配置建议
@Configuration
public class ProdCorsConfig implements WebMvcConfigurer {
@Value("${app.cors.allowed-origins}")
private String[] allowedOrigins;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(allowedOrigins)
.allowedMethods("GET", "POST")
.allowedHeaders("Content-Type", "Authorization")
.exposeHeaders("X-Custom-Header")
.allowCredentials(true)
.maxAge(3600);
}
}
4.2 结合Spring Security
当使用Spring Security时,需要确保CORS配置在安全过滤器之前:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and() // 启用Spring Security的CORS支持
.csrf().disable()
.authorizeRequests()
// 其他配置...
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://trusted.com"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
4.3 监控与日志
添加CORS请求日志:
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// ...配置
FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new CorsFilter(source) {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
log.info("CORS请求: {} {}", request.getMethod(), request.getRequestURI());
super.doFilterInternal(request, response, filterChain);
}
});
return registration;
}
五、测试与验证
5.1 测试类示例
@SpringBootTest
@AutoConfigureMockMvc
public class CorsTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testCorsHeaders() throws Exception {
mockMvc.perform(options("/api/users")
.header("Access-Control-Request-Method", "GET")
.header("Origin", "http://localhost:3000"))
.andExpect(header().exists("Access-Control-Allow-Origin"))
.andExpect(header().string("Access-Control-Allow-Methods", "GET"));
}
@Test
public void testActualRequest() throws Exception {
mockMvc.perform(get("/api/users")
.header("Origin", "http://localhost:3000"))
.andExpect(status().isOk())
.andExpect(header().string("Access-Control-Allow-Origin", "http://localhost:3000"));
}
}
5.2 使用CURL测试
检查OPTIONS预检请求:
curl -X OPTIONS http://localhost:8080/api/users \
-H "Origin: http://test.com" \
-H "Access-Control-Request-Method: GET" \
-I
检查实际请求:
curl -X GET http://localhost:8080/api/users \
-H "Origin: http://test.com" \
-I
六、总结与推荐方案
6.1 配置方式对比
方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
全局WebMvcConfigurer | 大多数应用 | 集中管理,支持细粒度配置 | 需要代码变更 |
过滤器方式 | 需要最高优先级处理 | 处理最早,避免被其他过滤器干扰 | 配置稍复杂 |
@CrossOrigin注解 | 特定接口需要特殊规则 | 精准控制 | 分散在各处,维护成本高 |
属性文件配置 | 简单需求,配置驱动 | 无需代码变更 | 灵活性较低 |
手动设置响应头 | 需要动态决定CORS头 | 最大灵活性 | 代码侵入性强 |
6.2 推荐方案
新项目:
- 使用
WebMvcConfigurer
全局配置 - 结合属性文件动态配置允许的源
- 对特殊接口使用
@CrossOrigin
覆盖全局设置
已有项目迁移:
- 先添加全局配置
- 逐步移除分散的注解配置
- 最终统一到1-2种管理方式
Spring Cloud微服务:
- 在API Gateway统一处理CORS
- 各微服务禁用CORS或仅允许网关源
- 结合OAuth2等安全机制
6.3 终极建议
- 生产环境不要使用
*
作为允许源 - 明确列出可信域名 - 限制允许的方法和头 - 按最小权限原则配置
- 合理设置maxAge - 平衡安全性和性能(建议1小时)
- 与前端团队协作 - 确保双方对CORS要求理解一致
- 监控CORS错误 - 及时发现配置问题或恶意请求
通过合理配置CORS,可以在保障安全性的同时,为现代前后端分离架构提供必要的跨域支持。Spring Boot提供了多种灵活的方式,开发者应根据项目实际需求选择最适合的方案。