前后端分离项目,跨域是必踩的坑。本文将彻底讲清跨域的本质、spring boot 4 的 cors 解决方案、全局/局部配置、与拦截器的执行顺序,以及生产环境的安全配置。
一、什么是跨域?为什么会有跨域问题?
同源策略(same-origin policy)
浏览器出于安全考虑,只允许同源的资源进行交互。
同源 = 协议 + 域名 + 端口 完全相同
| url | 是否同源 | 原因 |
|---|---|---|
http://localhost:8080 → http://localhost:8080/api | ✅ | 完全相同 |
http://localhost:8080 → https://localhost:8080 | ❌ | 协议不同 |
http://localhost:8080 → http://localhost:8081 | ❌ | 端口不同 |
http://localhost:8080 → http://api.example.com | ❌ | 域名不同 |
跨域请求被拦截的完整流程
浏览器 → 服务器:发送请求(实际已到达服务器)
服务器 → 浏览器:返回响应
浏览器:检查响应头是否有 cors 头
→ 有:放行,交给 js
→ 没有:拦截,报错
关键点:请求已经到达服务器,只是浏览器拦截了响应!
简单请求 vs 预检请求(options)
| 类型 | 触发条件 | 浏览器行为 |
|---|---|---|
| 简单请求 | get / post / head + 特定 content-type | 直接发送 |
| 预检请求 | put / delete / 自定义头 / application/json | 先发 options,通过后再发真实请求 |
二、spring boot 4 解决跨域的 4 种方式
方式一:@crossorigin 注解(局部)
@restcontroller
@requestmapping("/users")
@crossorigin(origins = "http://localhost:3000")
public class usercontroller {
@getmapping("/{id}")
@crossorigin(origins = "http://localhost:3000", maxage = 3600)
public result<uservo> getuser(@pathvariable long id) {
return result.success(userservice.getbyid(id));
}
}缺点:每个 controller 都要加,维护成本高
方式二:全局 cors 配置(推荐)
@configuration
public class corsconfig {
@bean
public corsfilter corsfilter() {
corsconfiguration config = new corsconfiguration();
config.setallowcredentials(true);
config.addallowedorigin("http://localhost:3000");
config.addallowedheader("*");
config.addallowedmethod("*");
config.setmaxage(3600l);
urlbasedcorsconfigurationsource source = new urlbasedcorsconfigurationsource();
source.registercorsconfiguration("/**", config);
return new corsfilter(source);
}
}方式三:webmvcconfigurer 配置(最优雅)
@configuration
public class webconfig implements webmvcconfigurer {
@override
public void addcorsmappings(corsregistry registry) {
registry.addmapping("/**")
.allowedorigins("http://localhost:3000")
.allowedmethods("get", "post", "put", "delete", "options")
.allowedheaders("*")
.allowcredentials(true)
.maxage(3600);
}
}企业级推荐:代码清晰、语义明确
方式四:spring security 中配置(有 security 时必用)
@configuration
@enablewebsecurity
public class securityconfig {
@bean
public securityfilterchain filterchain(httpsecurity http) throws exception {
http
.cors(cors -> cors.configurationsource(corsconfigurationsource()))
.csrf(csrf -> csrf.disable());
return http.build();
}
@bean
public corsconfigurationsource corsconfigurationsource() {
corsconfiguration config = new corsconfiguration();
config.setallowcredentials(true);
config.addallowedorigin("http://localhost:3000");
config.addallowedheader("*");
config.addallowedmethod("*");
urlbasedcorsconfigurationsource source = new urlbasedcorsconfigurationsource();
source.registercorsconfiguration("/**", config);
return source;
}
}三、cors 配置项详解
| 配置项 | 说明 | 推荐值 |
|---|---|---|
allowedorigins | 允许的源 | 具体域名,不要用 * |
allowedmethods | 允许的 http 方法 | get, post, put, delete, options |
allowedheaders | 允许的请求头 | * 或具体头 |
allowcredentials | 是否允许凭证(cookie) | true |
maxage | 预检请求缓存时间 | 3600 秒 |
重要限制:allowcredentials = true 时,allowedorigins 不能用 *
四、cors 与 filter / interceptor 的执行顺序
完整请求处理链
浏览器请求
↓
filter (corsfilter)
↓
dispatcherservlet
↓
interceptor (prehandle)
↓
controller
cors 在 filter 阶段处理,早于 interceptor 和 controller
为什么 interceptor 中校验 token 会失败?
原因:预检请求(options)不带 token
@override
public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) {
// ❌ options 请求会在这里被拦截
string token = request.getheader("authorization");
if (token == null) {
return false;
}
return true;
}正确写法:放行 options 请求
@override
public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) {
if ("options".equals(request.getmethod())) {
return true; // 放行预检请求
}
// 正常校验 token
string token = request.getheader("authorization");
return token != null && token.startswith("bearer ");
}五、生产环境安全配置(重点)
不安全的配置
config.addallowedorigin("*"); // 允许所有源
config.setallowcredentials(true); // 还允许凭证 → 报错生产级安全配置
@configuration
public class corsconfig {
private static final list<string> allowed_origins = list.of(
"https://www.example.com",
"https://admin.example.com",
"https://m.example.com"
);
@bean
public corsfilter corsfilter() {
corsconfiguration config = new corsconfiguration();
config.setallowcredentials(true);
config.setallowedorigins(allowed_origins); // 明确指定允许的源
config.addallowedmethod("get");
config.addallowedmethod("post");
config.addallowedmethod("put");
config.addallowedmethod("delete");
config.addallowedmethod("options");
config.addallowedheader("*");
config.setmaxage(3600l);
urlbasedcorsconfigurationsource source = new urlbasedcorsconfigurationsource();
source.registercorsconfiguration("/api/**", config); // 只针对 api
return new corsfilter(source);
}
}六、spring boot 4 中的新变化
对 allowedoriginpatterns 支持更完善(支持通配符模式)
aot 模式下 cors 配置处理更早
与 spring security 7 深度整合
七、常见坑位总结(血泪教训)
坑1:配置了 cors 但依然跨域报错
检查:
- 是否有拦截器拦截了 options 请求
allowcredentials与allowedorigins是否冲突- 前端是否真的发了请求
坑2:前端带 cookie 但后端收不到
检查:
- 后端
allowcredentials = true - 前端
withcredentials = true - 前端
axios.defaults.withcredentials = true
坑3:spring security 中 cors 不生效
必须在 security 中配置 cors,不能只靠 webmvcconfigurer
坑4:预检请求被拦截器拦截
放行 options 请求
坑5:allowedorigins 用通配符
生产环境明确指定域名
八、本篇总结
跨域是浏览器的同源策略限制,请求已到达服务器
spring boot 4 推荐用 webmvcconfigurer 配置 cors
cors 在 filter 阶段处理,早于 interceptor
options 预检请求必须放行
生产环境明确指定允许的源,不要用 *
以上就是springboot优雅解决cors跨域问题的完整方案的详细内容,更多关于springboot解决cors跨域的资料请关注代码网其它相关文章!
发表评论