1. 理解跨域问题的本质
在Web开发中,跨域问题就像两个不同国家之间的海关检查。想象一下,你在中国大陆的网站上点击一个按钮,这个按钮要向香港的服务器发送请求获取数据。虽然都是中国的领土,但浏览器会认为这是"跨国"操作,需要特殊的安全检查。
浏览器实施同源策略(Same-Origin Policy)的根本原因是为了防止恶意网站窃取用户数据。这个策略要求:只有当协议(http/https)、域名和端口都完全一致时,才允许自由通信。三者中有任何一个不同,就被视为跨域请求。
2. CORS机制的工作原理
CORS(Cross-Origin Resource Sharing)是现代浏览器支持的标准机制,它允许服务器声明哪些外部源可以访问自己的资源。这就像海关的绿色通道 - 服务器明确告诉浏览器:"这些来源的请求可以放行"。
一个完整的CORS请求流程包含以下几个关键步骤:
- 简单请求:直接发送实际请求,浏览器自动添加Origin头
- 预检请求:对于可能修改数据的复杂请求(POST/PUT/DELETE等),浏览器先发送OPTIONS请求
- 服务器响应:服务器返回适当的CORS头(Access-Control-Allow-*系列)
- 实际请求:通过预检后,浏览器发送实际请求
3. Spring中实现CORS的Filter方案
3.1 为什么选择Filter方案
在Spring生态中,实现CORS有多种方式,但Filter方案具有以下优势:
- 处理时机最早:Filter是Servlet容器层面的组件,能在请求最早阶段处理跨域
- 性能最优:避免了Spring MVC层的额外处理开销
- 适用范围广:不仅适用于Spring MVC,也适用于WebFlux等其他Web框架
- 配置灵活:可以精细控制每个请求的跨域行为
3.2 核心实现代码解析
下面是一个完整的CORS Filter实现示例:
import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component @Order(Ordered.HIGHEST_PRECEDENCE) public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; HttpServletRequest request = (HttpServletRequest) req; // 允许的源,生产环境应替换为具体域名 response.setHeader("Access-Control-Allow-Origin", "*"); // 允许的HTTP方法 response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, PATCH"); // 预检请求缓存时间(秒) response.setHeader("Access-Control-Max-Age", "3600"); // 允许的请求头 response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN"); // 是否允许携带凭证(cookie等) response.setHeader("Access-Control-Allow-Credentials", "true"); // 暴露给前端JS能获取的响应头 response.setHeader("Access-Control-Expose-Headers", "Authorization, Content-Disposition"); if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); } else { chain.doFilter(req, res); } } @Override public void init(FilterConfig filterConfig) { // 初始化逻辑(可选) } @Override public void destroy() { // 销毁逻辑(可选) } }3.3 关键配置项详解
Access-Control-Allow-Origin
最重要的头信息,指定允许访问资源的域。生产环境强烈建议替换通配符*为具体的可信域名列表。Access-Control-Allow-Methods
定义允许的HTTP方法,如GET、POST等。注意要包含OPTIONS方法用于预检。Access-Control-Allow-Headers
列出允许的自定义请求头,特别是认证相关的头如Authorization。Access-Control-Allow-Credentials
当需要传输cookie等凭证信息时,必须设置为true,且不能使用通配符*作为Origin。Access-Control-Expose-Headers
指定哪些响应头可以被前端JavaScript代码访问。
4. 生产环境最佳实践
4.1 安全配置建议
避免使用通配符
*
在生产环境中,应该明确指定允许的域名:String allowedOrigins = "https://yourdomain.com,https://api.yourdomain.com"; response.setHeader("Access-Control-Allow-Origin", allowedOrigins);限制HTTP方法
只开放必要的HTTP方法,减少攻击面:response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");启用CSRF保护
当允许凭证时,必须配合CSRF防护机制:response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, X-XSRF-TOKEN");
4.2 性能优化技巧
合理设置Max-Age
预检请求结果可以被浏览器缓存,减少OPTIONS请求:// 缓存1小时 response.setHeader("Access-Control-Max-Age", "3600");动态Origin检测
实现动态Origin白名单,兼顾安全与灵活性:String origin = request.getHeader("Origin"); if (isAllowedOrigin(origin)) { response.setHeader("Access-Control-Allow-Origin", origin); }避免重复处理
添加条件判断,避免对同一请求多次处理CORS头:if (!response.containsHeader("Access-Control-Allow-Origin")) { // 添加CORS头 }
5. 常见问题排查指南
5.1 预检请求失败
现象:浏览器控制台报错"Response to preflight request doesn't pass access control check"
解决方案:
- 确保OPTIONS请求返回200状态码
- 检查Access-Control-Allow-Headers是否包含请求中使用的所有自定义头
- 验证Access-Control-Allow-Methods是否包含请求方法
5.2 凭证(Cookie)无法传递
现象:设置了withCredentials=true但cookie未随请求发送
解决方案:
- 确认Access-Control-Allow-Credentials设置为true
- 确保Access-Control-Allow-Origin不是通配符
*,而是具体域名 - 检查Cookie的SameSite属性设置
5.3 响应头不可见
现象:前端无法通过getResponseHeader()获取自定义头
解决方案:
- 在服务器端通过Access-Control-Expose-Headers暴露需要的头
- 确保头名称拼写正确,区分大小写
6. 与其他Spring跨域方案的对比
6.1 @CrossOrigin注解
适合方法级别的细粒度控制,但有以下局限:
- 只对注解的方法/类生效
- 无法处理非Spring管理的端点
- 配置分散,维护成本高
6.2 WebMvcConfigurer
全局配置方式示例:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("https://yourdomain.com") .allowedMethods("GET", "POST"); } }优缺点:
- 配置集中,易于管理
- 但仅适用于Spring MVC场景
- 处理时机晚于Filter
6.3 响应头手动设置
在控制器方法中直接操作HttpServletResponse:
@GetMapping("/data") public ResponseEntity<?> getData(HttpServletResponse response) { response.setHeader("Access-Control-Allow-Origin", "*"); // ... }适用场景:
- 需要动态控制跨域的特定情况
- 不推荐作为主要方案,维护困难
7. 高级应用场景
7.1 微服务架构中的CORS
在Spring Cloud微服务体系中,通常有两种方案:
网关层统一处理
在API Gateway(如Spring Cloud Gateway)配置全局CORS,简化各服务配置服务各自处理
每个服务维护自己的CORS策略,灵活性更高但管理复杂
7.2 WebSocket跨域
WebSocket连接同样受同源策略限制,需要在建立连接时处理:
@Configuration public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(myHandler(), "/ws") .setAllowedOrigins("https://yourdomain.com"); } }7.3 文件上传跨域
处理文件上传时需要特别注意:
- 确保multipart/form-data请求能被预检
- 可能需要额外暴露Content-Disposition头以下载文件
8. 测试与验证方法
8.1 使用CURL测试
验证CORS头是否正确的简单命令:
curl -H "Origin: http://example.com" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: X-Requested-With" \ -X OPTIONS --verbose http://yourserver.com/api8.2 Postman测试技巧
虽然Postman不受同源策略限制,但可以:
- 手动添加Origin头模拟跨域请求
- 检查响应头中是否包含CORS相关头
- 特别测试OPTIONS预检请求
8.3 浏览器端验证
前端开发者可以检查:
- 网络请求中是否自动添加了Origin头
- 预检请求和实际请求的流程是否符合预期
- 控制台是否有CORS相关错误
9. 安全防护补充
9.1 CSRF与CORS的协同
当启用CORS且允许凭证时,必须实施CSRF防护:
- 使用Spring Security的CSRF保护
- 配合自定义的XSRF-TOKEN头
- 确保CORS配置允许必要的安全头
9.2 速率限制
对OPTIONS请求也应实施速率限制,防止DDoS攻击:
if ("OPTIONS".equals(request.getMethod())) { rateLimiter.tryAcquire(); }9.3 日志监控
记录异常的CORS请求有助于发现攻击尝试:
String origin = request.getHeader("Origin"); if (!isAllowedOrigin(origin)) { log.warn("Blocked CORS request from origin: {}", origin); }10. 实际项目中的经验总结
在大型电商平台项目中,我们采用以下CORS策略:
分层配置
- 网关层:基础CORS设置,允许常见HTTP方法
- 业务服务:细粒度控制,如支付服务只允许POST
动态白名单
从数据库读取允许的域名列表,支持热更新监控告警
对异常的Origin来源触发安全告警性能优化
对静态资源设置更长的Max-Age缓存时间
特别提醒:在Spring Boot 2.4+版本中,如果同时使用了Spring Security,需要注意其CORS配置会覆盖自定义Filter的设置。此时需要在Security配置中明确指定CORS来源:
@EnableWebSecurity public class SecurityConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.cors(cors -> cors.configurationSource(request -> { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("https://yourdomain.com")); config.setAllowedMethods(List.of("GET","POST")); return config; })); return http.build(); } }对于非Spring Boot的传统Spring MVC项目,需要在web.xml中配置Filter:
<filter> <filter-name>corsFilter</filter-name> <filter-class>com.yourpackage.CorsFilter</filter-class> </filter> <filter-mapping> <filter-name>corsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>最后,关于浏览器兼容性需要注意:虽然现代浏览器都支持CORS,但某些旧版本(如IE9)对CORS的支持有限。如果必须支持这些浏览器,可能需要考虑JSONP等备选方案,但这已超出本文讨论范围。