1. 跨域问题与CORS基础解析
当你在开发前后端分离的Web应用时,经常会遇到这样的场景:前端页面运行在http://localhost:8080,而后端API服务部署在http://api.example.com。此时浏览器控制台会抛出经典的跨域错误:"No 'Access-Control-Allow-Origin' header is present on the requested resource"。这就是臭名昭著的跨域问题(CORS)在作祟。
跨域问题的本质是浏览器的同源策略(Same-Origin Policy)限制。这个安全机制规定:默认情况下,网页中的脚本只能访问与当前页面同源(协议+域名+端口完全相同)的资源。虽然这保护了用户安全,但也给前后端分离开发带来了麻烦。
CORS(Cross-Origin Resource Sharing)正是为解决这个问题而生的W3C标准。它通过特殊的HTTP头来实现跨域访问控制。一个典型的CORS请求流程是这样的:
- 浏览器发现当前请求是跨域请求时,会自动在请求头中添加Origin字段
- 服务端检查Origin,如果允许该来源,则在响应头中包含Access-Control-Allow-Origin
- 浏览器收到响应后验证Access-Control-Allow-Origin,匹配则允许前端代码访问响应内容
2. Spring中实现CORS的三种方式
2.1 注解方式:@CrossOrigin
对于简单的场景,Spring提供了极简的注解方案。你可以在Controller类或方法上添加@CrossOrigin注解:
java复制@RestController
@CrossOrigin(origins = "http://localhost:8080")
public class MyController {
@GetMapping("/api/data")
@CrossOrigin(maxAge = 3600)
public ResponseEntity<Data> getData() {
// ...
}
}
这种方式适合:
- 快速原型开发
- 只有少量端点需要跨域
- 跨域配置较为简单的情况
注意:在Spring Security环境中,仅使用@CrossOrigin可能还不够,需要结合安全配置。
2.2 全局配置:WebMvcConfigurer
当需要统一管理跨域配置时,实现WebMvcConfigurer接口是更优雅的方案:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("GET", "POST", "PUT")
.allowCredentials(true)
.maxAge(3600);
}
}
这种方式的优势在于:
- 集中管理所有跨域规则
- 支持路径模式匹配
- 可以精细控制每个规则的各项参数
2.3 过滤器方案:CorsFilter
对于需要最大灵活性的场景,特别是与Spring Security集成时,自定义过滤器是最强大的选择。以下是完整的CorsFilter实现:
java复制@Component
public class CustomCorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "authorization, content-type");
response.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(request, response);
}
}
}
3. CORS配置的进阶技巧
3.1 动态Origin控制
实际项目中,我们经常需要根据环境动态配置允许的Origin。可以通过从配置文件中读取来实现:
java复制@Value("${cors.allowed-origins}")
private String[] allowedOrigins;
// 在过滤器中
if (Arrays.asList(allowedOrigins).contains(origin)) {
response.setHeader("Access-Control-Allow-Origin", origin);
}
3.2 预检请求(Preflight)处理
对于非简单请求(如Content-Type为application/json的POST请求),浏览器会先发送OPTIONS预检请求。服务端必须正确处理:
java复制if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT");
response.setHeader("Access-Control-Allow-Headers", "content-type");
response.setStatus(HttpServletResponse.SC_OK);
return;
}
3.3 与Spring Security集成
当项目使用Spring Security时,需要在安全配置中明确允许OPTIONS请求:
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
// 其他安全配置...
}
4. 常见问题排查指南
4.1 为什么设置了CORS仍然报错?
可能原因:
- 响应头中Access-Control-Allow-Origin的值与请求头中的Origin不匹配
- 缺少必要的头部如Access-Control-Allow-Methods
- 使用了不允许的HTTP方法或Headers
解决方案:
- 使用浏览器开发者工具检查请求和响应头
- 确保服务端配置覆盖了所有需要的端点
4.2 如何处理带凭证(Credentials)的请求?
当请求需要携带Cookie或认证信息时:
- 客户端需要设置withCredentials为true
- 服务端必须设置Access-Control-Allow-Credentials: true
- Access-Control-Allow-Origin不能使用通配符*
4.3 生产环境CORS最佳实践
- 不要使用通配符(*)允许所有来源
- 根据环境配置文件管理允许的Origin列表
- 限制允许的HTTP方法和Headers到最小必要集合
- 设置合理的Max-Age减少预检请求
5. 性能优化与安全考量
5.1 缓存预检请求
通过设置适当的Access-Control-Max-Age(单位秒),可以让浏览器缓存预检响应:
java复制response.setHeader("Access-Control-Max-Age", "86400"); // 24小时
5.2 细粒度的Header控制
只暴露必要的响应头:
java复制response.setHeader("Access-Control-Expose-Headers", "X-Custom-Header");
5.3 CSRF防护与CORS
虽然CORS提供了一定保护,但仍需注意:
- 敏感操作应使用CSRF Token
- 重要API应考虑添加额外的认证机制
- 对于特别敏感的数据,最好避免跨域访问
在实现Spring CORS解决方案时,我强烈建议从简单方案开始,随着需求复杂化逐步升级。对于大多数项目,WebMvcConfigurer方案已经足够。只有在需要与复杂安全策略集成,或者需要动态控制CORS行为时,才考虑自定义过滤器方案。