1. Spring跨域CORS Filter实战解析
前后端分离架构下,跨域问题就像一道无形的墙,把前端请求挡在门外。最近在重构一个老项目时,我再次遇到了经典的CORS报错:"has been blocked by CORS policy"。作为Spring开发者,实现一个健壮的CORS Filter是必备技能。下面分享我在实际项目中沉淀的解决方案。
CORS(Cross-Origin Resource Sharing)本质是一种安全机制,现代浏览器默认遵循同源策略。当你的前端应用运行在http://localhost:8080,而后端API在http://api.example.com时,浏览器会拦截跨域请求。Spring提供了多种CORS解决方案,而Filter方案是最灵活可控的一种。
2. CORS核心机制与Spring处理方案
2.1 预检请求(Preflight)全解析
当浏览器发送非简单请求(如Content-Type为application/json的POST)时,会先发起OPTIONS预检请求。这个过程就像海关检查:
- 浏览器发送OPTIONS请求,携带
Origin、Access-Control-Request-Method等头部 - 服务端需响应
Access-Control-Allow-*系列头部 - 通过检查后,浏览器才发送真实请求
常见报错如"response to preflight request doesn't pass"就是预检阶段失败。我曾遇到Edge浏览器特有的"request client is not a secure context"错误,最终发现是本地开发环境未使用HTTPS导致。
2.2 Spring中的三种CORS实现对比
| 方案 | 适用场景 | 优缺点对比 |
|---|---|---|
| @CrossOrigin注解 | 简单Controller方法级控制 | 配置简单但无法全局生效 |
| WebMvcConfigurer | 基于Spring MVC的全局配置 | 支持路径匹配但不够底层 |
| Filter方案 | 需要精细控制的生产环境 | 完全掌控请求/响应流程 |
经过多个项目验证,Filter方案在微服务架构下表现最优。特别是在需要动态配置CORS规则,或要整合Spring Security时,Filter是唯一选择。
3. 手写CORS Filter完整实现
3.1 基础版Filter代码实现
java复制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", "*");
response.setHeader("Access-Control-Allow-Methods",
"POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
"Content-Type, Authorization, X-Requested-With");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
}
关键提示:生产环境绝对不要使用
*作为Allow-Origin值,这会导致严重的安全漏洞。此处仅用于演示简化。
3.2 注册Filter的两种方式
方案一:传统web.xml配置(适合老项目)
xml复制<filter>
<filter-name>corsFilter</filter-name>
<filter-class>com.example.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>corsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
方案二:Spring Boot Bean注册(推荐新项目)
java复制@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> registration =
new FilterRegistrationBean<>();
registration.setFilter(new CorsFilter());
registration.addUrlPatterns("/*");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE); // 确保最先执行
return registration;
}
4. 生产级CORS解决方案
4.1 动态域名白名单实现
实际项目中,我通常会这样实现动态白名单:
java复制// 从数据库或配置中心加载允许的域名列表
List<String> allowedOrigins = loadAllowedOrigins();
String originHeader = request.getHeader("Origin");
if (allowedOrigins.contains(originHeader)) {
response.setHeader("Access-Control-Allow-Origin", originHeader);
response.setHeader("Vary", "Origin"); // 重要!避免缓存污染
}
4.2 与Spring Security的整合技巧
当项目使用Spring Security时,CORS Filter需要特殊处理:
- 确保Filter在Security过滤器链之前执行
- 需要额外配置
http.cors()启用Spring Security的CORS支持 - 特别注意CSRF与CORS的冲突问题
典型配置示例:
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().configurationSource(corsConfigurationSource())
.and()
.csrf().disable() // 跨域时通常需要禁用CSRF
...;
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("https://trusted.com"));
config.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
5. 疑难问题排查手册
5.1 高频报错解决方案
| 错误现象 | 根本原因 | 解决方案 |
|---|---|---|
| 预检请求返回403 | 未正确处理OPTIONS方法 | 在Filter中单独处理OPTIONS请求 |
| 响应头缺失Vary | 缓存服务器错误缓存CORS响应 | 添加Vary: Origin响应头 |
| 带Cookie请求失败 | 未设置Allow-Credentials | 添加Access-Control-Allow-Credentials: true |
| Edge浏览器特有错误 | 安全上下文限制 | 开发环境启用HTTPS |
5.2 性能优化实践
- 预检请求缓存:合理设置
Access-Control-Max-Age(建议2小时) - 避免过度检查:对静态资源设置更宽松的CORS策略
- 响应头精简:非必要不添加CORS头,减少网络开销
6. 前沿趋势与Spring AI整合
随着Spring AI 2.0的发布,在处理AI服务跨域时有了新思路。比如对接通义千问API时:
java复制@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/ai/**")
.allowedOrigins("https://qianwen.aliyun.com")
.allowCredentials(true);
}
};
}
对于使用Spring WebFlux的项目,推荐使用CorsWebFilter:
java复制@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
在微服务架构下,更好的做法是在API Gateway层统一处理CORS,避免每个服务重复配置。这需要根据具体技术栈(如Spring Cloud Gateway)进行针对性实现。
