1. 跨域问题的本质与Spring Boot应对策略
当我们在浏览器中开发前后端分离的Web应用时,经常会遇到这样的报错:"No 'Access-Control-Allow-Origin' header is present on the requested resource"。这就是典型的跨域问题(CORS),它源于浏览器的同源策略(Same-Origin Policy)安全机制。
同源策略要求请求的协议、域名和端口完全一致才允许资源交互。比如:
http://a.com→http://a.com/api✅ 同源http://a.com→https://a.com/api❌ 协议不同http://a.com:8080→http://a.com:8090/api❌ 端口不同http://a.com→http://b.com/api❌ 域名不同
在Spring Boot项目中,我们通常采用以下四种主流方案解决跨域问题,每种方案各有其适用场景:
2. 方案一:使用@CrossOrigin注解
2.1 方法级细粒度控制
java复制@RestController
@RequestMapping("/api")
public class UserController {
@CrossOrigin(origins = "http://localhost:3000")
@GetMapping("/users")
public List<User> getUsers() {
// 业务逻辑
}
}
这种方式的优势在于:
- 精确控制单个接口的跨域权限
- 支持配置allowedOrigins、methods等属性
- 适合需要差异化配置的接口
2.2 类级统一配置
java复制@CrossOrigin(origins = "http://localhost:3000", maxAge = 3600)
@RestController
@RequestMapping("/api")
public class ProductController {
// 所有方法继承类级配置
}
注意:当类和方法同时存在注解时,方法级配置会覆盖类级配置
3. 方案二:全局CORS配置
3.1 基于WebMvcConfigurer(Spring MVC)
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST")
.allowCredentials(true)
.maxAge(3600);
}
}
3.2 基于WebFluxConfigurer(Spring WebFlux)
java复制@Configuration
public class CorsConfig implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*");
}
}
全局配置的特点:
- 一次性解决所有接口跨域问题
- 支持路径模式匹配(如
/api/**) - 可以集中管理credentials、headers等复杂配置
4. 方案三:过滤器(Filter)实现
4.1 自定义CORS过滤器
java复制@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
chain.doFilter(req, res);
}
}
4.2 过滤器方案的适用场景
- 需要与第三方系统深度集成时
- 对请求/响应有特殊处理需求
- 需要兼容非Spring管理的端点
重要提示:如果使用Spring Security,需要确保过滤器顺序正确
5. 方案四:Spring Security集成方案
5.1 安全框架下的CORS配置
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().configurationSource(corsConfigurationSource())
.and()
// 其他安全配置
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
5.2 安全注意事项
- 生产环境避免使用
allowedOrigins("*") - 敏感接口应结合CSRF保护
- 预检请求(OPTIONS)需要特殊处理
6. 方案对比与选型建议
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| @CrossOrigin | 需要细粒度控制 | 配置简单,直观 | 重复配置多 |
| 全局CORS配置 | 统一管理所有接口 | 维护方便,一致性高 | 不够灵活 |
| 过滤器 | 非Spring管理端点 | 完全控制请求/响应 | 需要处理底层细节 |
| Spring Security集成 | 已使用安全框架的项目 | 与权限系统深度集成 | 配置复杂度高 |
实际项目中,我通常会采用组合方案:
- 开发环境使用全局配置快速验证
- 生产环境采用Spring Security方案
- 特殊接口用@CrossOrigin补充配置
7. 常见问题排查指南
7.1 预检请求(OPTIONS)失败
现象:浏览器发送OPTIONS请求返回403
解决方案:
java复制// 在Security配置中放行OPTIONS方法
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll();
7.2 携带Cookie时失效
现象:设置了withCredentials但请求被拒绝
解决方法:
java复制// 必须指定具体origin,不能用"*"
configuration.setAllowedOrigins(Arrays.asList("http://client.com"));
configuration.setAllowCredentials(true);
7.3 响应头未生效
检查顺序:
- 是否有其他过滤器覆盖了CORS头
- 是否配置了多个CORS方案导致冲突
- 浏览器缓存了旧的CORS策略(尝试强制刷新)
8. 生产环境最佳实践
- Origin白名单:通过配置中心动态管理
java复制@Value("${cors.allowed-origins}")
private String[] allowedOrigins;
// 在配置类中使用
configuration.setAllowedOrigins(Arrays.asList(allowedOrigins));
- 监控与告警:记录异常的CORS请求
java复制@Slf4j
@Component
public class CorsLoggerFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) {
String origin = request.getHeader("Origin");
if(origin != null && !isAllowed(origin)) {
log.warn("Blocked CORS request from: {}", origin);
}
// ...
}
}
- 性能优化:合理设置maxAge
java复制// 根据业务场景设置缓存时间(秒)
configuration.setMaxAge(86400L); // 24小时
在微服务架构中,建议通过API网关统一处理CORS,避免每个服务重复配置。对于特别复杂的跨域场景(如WebSocket跨域),可能需要结合Nginx反向代理来实现。