Java后端跨域处理全解析:从CORS原理到Spring Boot实战案例
目录导读
- 跨域问题的本质:浏览器的同源策略
- CORS机制核心概念:预检请求与响应头
- Java生态主流跨域处理方案对比
- Spring Boot 3.x 跨域配置实战(含Filter与注解)
- 复杂场景问答:携带Cookie、自定义Header、放行精确域名
- 常见坑点排查:为什么配置了还是报跨域错误?
跨域问题的本质:浏览器的同源策略
浏览器出于安全考虑,会阻止协议、域名、端口任一不同的资源请求,这就是“同源策略”,例如前端站点http://a.com:8080访问后端接口http://localhost:8081/api,即构成跨域,需要明确:跨域限制是浏览器的行为,并非服务端拒绝请求,实际请求已经到达后端,只是响应被浏览器拦截。

理解这一点至关重要,因为这意味着后端必须显式告知浏览器“我允许该源访问”,从而解除拦截。
CORS机制核心概念:预检请求与响应头
跨域资源共享(CORS)是W3C标准,通过在后端响应中添加特定HTTP头来声明允许的跨域策略。
- 简单请求(如GET/POST,且Content-Type为
application/x-www-form-urlencoded等):直接发送,但响应须包含Access-Control-Allow-Origin。 - 预检请求(Preflight):当请求包含自定义Header、或使用PUT/DELETE等非简单方法时,浏览器会先发送一个
OPTIONS请求,询问服务器允许的规则,服务器必须正确响应OPTIONS,并返回关键的三个头:Access-Control-Allow-Origin(必填,指定允许的源,或)Access-Control-Allow-Methods(允许的方法,如GET, POST, PUT, DELETE)Access-Control-Allow-Headers(允许的请求头,如Content-Type, Authorization)
Java生态主流跨域处理方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
Spring @CrossOrigin 注解 |
单个Controller或方法级 | 快速、直观 | 配置分散,不利于全局管理 |
| 全局CorsFilter/WebMvcConfigurer | 整个应用统一策略 | 集中管理,灵活度高 | 需注意过滤器执行顺序 |
| Nginx反向代理 | 生产环境前后端分离 | 前端无感,性能好 | 需额外运维配置,与Java代码解耦 |
| JSONP | 仅限GET请求老系统 | 简单 | 有安全漏洞,已不推荐 |
推荐优先使用Spring的WebMvcConfigurer全局配置,配合@CrossOrigin做局部细粒度覆盖。
Spring Boot 3.x 跨域配置实战(含Filter与注解)
案例背景:前端http://admin.example.com:3000需访问后端http://api.internal.cn:8081。
方案A:全局CorsConfigurer(推荐)
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 拦截所有路径
.allowedOrigins("http://admin.example.com:3000") // 精确放行,勿用*
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("Authorization") // 前端需要读取的响应头
.allowCredentials(true) // 允许携带Cookie或Authorization
.maxAge(3600); // 预检请求缓存1小时,减少OPTIONS请求
}
}
方案B:基于Filter泛化处理(适合非Spring MVC或自研框架)
@Component
public class SimpleCorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "http://admin.example.com:3000");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
response.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
return; // 直接拦截OPTIONS,不进入业务逻辑
}
chain.doFilter(req, res);
}
}
方案C:局部注解(用于某个特殊接口)
@RestController
@CrossOrigin(origins = "https://special.vip.com", methods = {RequestMethod.GET})
public class SpecialController { ... }
注意:若同时配置了全局与注解,注解会覆盖全局配置,应避免对同一路径重复定义。
复杂场景问答:携带Cookie、自定义Header、放行精确域名
*问:配置了`allowedOrigins("")`,但前端需求要携带Cookie,为什么浏览器还是报错?**
答:CORS规范规定,当allowCredentials=true时,Access-Control-Allow-Origin不能为,必须指定明确的源地址,这是安全策略,防止恶意站点利用Cookie伪造身份,必须改为明确的前端源地址,如http://admin.example.com:3000。
问:前端请求中带了X-Requested-With: XMLHttpRequest或自定义Token头,后端仍需处理吗?
答:是的,如果未在allowedHeaders中显式声明该Header,预检请求会失败,应配置allowedHeaders("*")或指定具体头名称,特别注意,如需暴露某些响应头给前端JS读取(如Authorization),需加上.exposedHeaders("Authorization")。
问:为什么我的PUT请求总是发送两次(一次OPTIONS,一次PUT)?
答:这是浏览器预检机制的正常表现,第一次OPTIONS用于“试探”,确定服务器允许跨域后,浏览器才发送真实PUT请求,可通过.maxAge(3600)缓存预检结果,减少OPTIONS请求次数。
常见坑点排查:为什么配置了还是报跨域错误?
-
过滤器优先级:如果你自定义了登录鉴权Filter,且该Filter先于CorsFilter执行,当返回401/403错误时,响应头中缺少CORS信息,浏览器同样拦截,解决办法:确保CorsFilter的执行顺序在最前(如
@Order(Ordered.HIGHEST_PRECEDENCE))。 -
网关/反向代理丢失响应头:若经过Nginx或Spring Cloud Gateway转发,需确认代理层没有丢弃或覆盖
Access-Control-Allow-*头。 -
简单请求不触发预检但仍被拦截:检查
Access-Control-Allow-Origin返回值是否与前端Origin完全一致,不能有多余空格。 -
错误使用
HttpServletResponse直接写值:在拦截器中直接对响应对象setHeader,但未通过CorsRegistry配置,后续请求可能被Spring MVC覆盖。