1. SpringBoot 3.x跨域难题的破局之道
上周排查线上问题时,发现前端同事在调用接口时频繁遇到"has been blocked by CORS policy"报错。这个看似简单的跨域问题,在SpringBoot 3.x中其实藏着不少新特性与陷阱。经过通宵调试和源码追踪,我总结出这套覆盖90%实际场景的CORS配置方案。
跨域问题本质是浏览器同源策略的安全限制。当你的前端应用(如http://localhost:3000)尝试访问后端API(如http://api.yourdomain.com)时,浏览器会先发送OPTIONS预检请求。只有当后端返回正确的CORS头,实际请求才会继续。在微服务架构下,这个问题会变得更加复杂。
2. 基础配置:三种实现方式对比
2.1 注解式配置(适合简单场景)
在Controller类或方法上添加@CrossOrigin是最快捷的方式:
java复制@RestController
@CrossOrigin(origins = "http://localhost:3000",
maxAge = 3600,
allowedHeaders = "*")
public class UserController {
@GetMapping("/users")
public List<User> getUsers() {
//...
}
}
注意:maxAge单位是秒,设置过大会导致浏览器缓存策略失效。生产环境建议控制在1800秒(30分钟)
2.2 全局配置(推荐主流方案)
创建配置类实现WebMvcConfigurer接口:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://yourdomain.com", "http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true)
.exposedHeaders("X-Custom-Header");
}
}
关键参数说明:
allowCredentials(true):允许携带cookie时,origins不能为"*"exposedHeaders:前端需要访问的非标准响应头
2.3 过滤器方案(最灵活控制)
适用于需要动态判断源等复杂场景:
java复制@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*"); // SpringBoot 2.4+新特性
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(1800L);
source.registerCorsConfiguration("/**", config);
return new FilterRegistrationBean<>(new CorsFilter(source));
}
三种方案对比表:
| 方案类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 注解式 | 特定接口需要特殊配置 | 精准控制 | 重复配置多 |
| 全局配置 | 统一规则的主流业务 | 维护方便 | 动态能力弱 |
| 过滤器 | 需要复杂逻辑判断 | 完全控制 | 实现成本高 |
3. Spring Security环境下的特殊处理
当项目引入Spring Security时,常规配置可能失效。这是因为Security过滤器链优先级更高:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.cors(c -> c.configurationSource(corsConfigurationSource()))
// 其他安全配置...
return http.build();
}
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}
必须注意的坑点:
- 同时存在WebMvcConfigurer和Security配置时,后者优先级更高
- 开启CSRF防护时,需要额外处理OPTIONS请求
- 认证接口的CORS头需要在成功响应中返回
4. 生产环境进阶配置技巧
4.1 动态源管理
通过自定义CorsConfigurationSource实现动态源控制:
java复制public class DynamicCorsSource implements CorsConfigurationSource {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration config = new CorsConfiguration();
// 从数据库或缓存读取允许的源
Set<String> allowedOrigins = originService.getAllowedOrigins();
config.setAllowedOrigins(new ArrayList<>(allowedOrigins));
// 其他配置...
return config;
}
}
4.2 预检请求缓存优化
通过Nginx层添加缓存头减少OPTIONS请求:
nginx复制location /api {
if ($request_method = OPTIONS) {
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain charset=UTF-8';
add_header Content-Length 0;
return 204;
}
}
4.3 监控与告警
在Filter中记录异常CORS请求:
java复制public class CorsMonitoringFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
String origin = request.getHeader("Origin");
if (!isAllowed(origin)) {
metrics.increment("cors.rejected");
logger.warn("Rejected CORS request from: {}", origin);
}
chain.doFilter(request, response);
}
}
5. 常见问题排坑指南
5.1 为什么配置了但还是报错?
典型症状排查表:
| 报错信息 | 可能原因 | 解决方案 |
|---|---|---|
| Missing 'Access-Control-Allow-Origin' | 配置未生效 | 检查过滤器顺序 |
| Credential not supported | 使用通配符*时带了cookie | 设置具体域名 |
| Method not allowed | OPTIONS未放行 | 添加OPTIONS到allowedMethods |
| Header not allowed | 前端带了自定义头 | 配置allowedHeaders |
5.2 本地开发环境特殊处理
在application-dev.yml中添加:
yaml复制spring:
mvc:
cors:
allowed-origins: "http://localhost:3000,http://127.0.0.1:3000"
allowed-methods: "*"
5.3 文件下载的特殊处理
当实现文件下载时,需要额外配置:
java复制@GetMapping("/download")
public ResponseEntity<Resource> downloadFile() {
// 文件资源准备...
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"file.txt\"")
.header("Access-Control-Expose-Headers", HttpHeaders.CONTENT_DISPOSITION)
.body(resource);
}
6. 安全加固建议
-
不要无脑使用
allowedOriginPattern("*"),应该:- 生产环境配置精确域名
- 使用正则表达式限定子域名(如
https://*.yourdomain.com)
-
敏感接口建议关闭CORS,通过API网关转发
-
定期审计CORS配置,防止过度开放
-
对于内部系统,可以考虑使用反向代理避免CORS问题
我在金融级项目中验证过的配置组合是:Nginx层做基础CORS控制 + 应用层做精细化管理 + 独立网关服务处理跨域认证。这种分层方案既能保证灵活性,又不会破坏安全边界。
