Java Web开发中CORS跨域问题的深度解析与实战配置
一、Spring Boot 环境下的 CORS 全局配置
在 Spring Boot 项目中,处理跨域资源共享(CORS)通常有两种主流的全局配置方式。开发者可根据项目的具体架构选择最合适的方案。
1. 基于 WebMvcConfigurer 的映射配置
通过实现 WebMvcConfigurer 接口并重写 addCorsMappings 方法,可以快速完成跨域规则的定义。
@Configuration
public class WebCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.exposedHeaders(HttpHeaders.AUTHORIZATION)
.maxAge(7200);
}
}
2. 基于 CorsFilter 的 Bean 注册
如果项目中使用了 Spring Security 或需要更底层的控制,直接注册 CorsFilter 是更稳妥的选择。
@Configuration
public class FilterCorsConfig {
@Bean
public FilterRegistrationBean<CorsFilter> corsFilterRegistration() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
二、传统 SSM 架构的 CORS 处理
在传统的 Spring + SpringMVC + MyBatis (SSM) 架构中,由于缺乏 Spring Boot 的自动配置能力,我们需要手动编写 Servlet 过滤器并在 web.xml 中进行注册。
1. 编写自定义跨域过滤器
public class LegacyCorsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Origin", httpRequest.getHeader("Origin"));
httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
httpResponse.setHeader("Access-Control-Max-Age", "3600");
httpResponse.setHeader("Access-Control-Allow-Headers",
"Content-Type, Authorization, X-Requested-With, Accept, Origin");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
// 针对浏览器发起的 OPTIONS 预检请求,直接返回 200 状态码
if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
httpResponse.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
}
2. 在 web.xml 中注册过滤器
<filter>
<filter-name>legacyCorsFilter</filter-name>
<filter-class>com.example.config.LegacyCorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>legacyCorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
三、Spring Boot 中 CORS 配置失效的排查与修复
在实际开发中,经常会遇到全局 CORS 配置不生效的问题。这通常是因为项目中的自定义拦截器(Interceptor)或安全过滤器拦截了请求,且未正确传递 CORS 响应头。以下是几种有效的修复策略。
策略 1:提升 CORS 过滤器的执行优先级
确保 CORS 过滤器在所有自定义拦截器之前执行。可以通过 FilterRegistrationBean 设置最高优先级。
@Bean
public FilterRegistrationBean<CorsFilter> prioritizedCorsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.setAllowCredentials(true);
config.addAllowedMethod("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
// 设置最高优先级,确保在 Token 校验等拦截器之前执行
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
策略 2:在自定义拦截器中手动注入 CORS 响应头
如果某些业务拦截器(如 Token 鉴权拦截器)必须在 CORS 过滤器之前执行,或者拦截器直接阻断了请求(如返回 401),则需要在拦截器内部手动添加跨域头。
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 手动注入跨域响应头
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
// 放行 OPTIONS 预检请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return false;
}
// 业务鉴权逻辑
String token = request.getHeader("Authorization");
if (token == null || !validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"error\":\"Unauthorized\"}");
return false;
}
return true;
}
}
策略 3:使用 @CrossOrigin 进行细粒度控制
对于不需要全局跨域,或者特定接口需要特殊跨域规则的场景,可以直接在 Controller 类或方法上使用 @CrossOrigin 注解。
@RestController
@RequestMapping("/secure-api")
@CrossOrigin(origins = "https://trusted-domain.com", allowCredentials = "true")
public class SecureController {
@GetMapping("/data")
public ResponseEntity<String> getSecureData() {
return ResponseEntity.ok("Secure Data");
}
}
四、兼容凭证(Cookie)与通配符 Origin 的现代方案
在早期的 Spring 版本中,如果设置了 allowCredentials(true),则不允许将 allowedOrigins 设置为 "*",这会引发配置冲突。从 Spring Framework 5.3(对应 Spring Boot 2.4+)开始,引入了 allowedOriginPatterns 属性来完美解决这一问题。
@Bean
public CorsConfigurationSource advancedCorsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 使用 Pattern 支持通配符,同时兼容 Credentials
configuration.addAllowedOriginPattern("*");
configuration.setAllowCredentials(true);
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
configuration.addExposedHeader(HttpHeaders.SET_COOKIE);
configuration.setMaxAge(86400L); // 缓存预检请求结果 24 小时
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
这种配置方式不仅避免了旧版本中的启动异常,还能灵活匹配复杂的子域名跨域需求(例如 *.example.com),是目前 Spring Boot 项目中处理全局跨域的最佳实践。