尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spring Boot + MyBatis跨域配置实战:从同源策略到CORS解决方案

Spring Boot + MyBatis跨域配置实战:从同源策略到CORS解决方案 做后端开发的这几年几乎每个人都遇到过特别磨人的场景前端同事突然甩过来一句“接口通了但浏览器报跨域了”附带一张红彤彤的报错截图。你自己拿 Postman 一测接口好得很数据嗖嗖地返回可一放进浏览器里的 Ajax 请求就像被一堵看不见的墙挡住连后端日志里都看不到这条请求。这堵墙就是跨域问题。这篇文章围绕 Spring Boot MyBatis 项目里最常遇到的跨域问题展开把“跨域是什么、为什么会出问题、有哪些解决办法、Spring Boot 里怎么配、面试怎么答”一次性讲透。我尽量用项目里实际踩过的坑来举例而不是给一堆理论。如果你是刚接触前后端分离开发的初学者或者准备面试的 Java 开发又或者被线上跨域 Bug 逼得焦头烂额这篇文章应该能帮你解决问题。1. 跨域请求到底是什么从同源策略说起1.1 先搞清楚“源”和“同源”这两个概念很多同学一上来就看“跨域”两个字其实关键在“同源”的反面。一个网页的“源”由三部分组成协议protocol、域名host/domain和端口port。只要这三者完全一致就是同源任何一个不同就是跨源也就是我们常说的跨域。举个例子当前页面地址http://localhost:8080/index.html 请求接口地址http://localhost:9090/api/users协议都是 http但端口一个是 8080一个是 9090端口不同这就是跨域。同理http 和 https 不同是跨域www.example.com 和 api.example.com 域名不同也是跨域包括顶级域名相同但二级域名不同的情况比如a.example.com和b.example.com依然是跨域。以上是从浏览器视角判定的“源”。注意这里有一个关键点跨域限制是浏览器行为不是服务端行为也不是 HTTP 协议本身的行为。服务器和服务器之间请求根本不存在跨域问题你用命令行工具 curl 请求接口也不会有跨域问题。只有当浏览器里的 JavaScript 代码发起跨域请求时浏览器才会按同源策略进行拦截。这个理解特别重要因为后端排查的时候经常发现接口没问题日志里也看不到请求原因就是请求根本没到达服务器或者到达了但浏览器把响应拦截了。1.2 同源策略浏览器的“安检系统”同源策略Same-Origin Policy是浏览器最核心的安全机制之一说白了就是浏览器默认不允许一个源里的脚本去读取另一个源里的数据。它的存在是为了防止恶意网站通过脚本窃取你在其他网站上的数据。打个比方同源策略像小区门口的保安你是 A 栋的住户想去 B 栋串门保安拦住你问“你是不是 B 栋的有没有门禁卡”没有门禁卡你就进不去。这里的门禁卡就是“同源”条件。但注意浏览器并不是完全禁止跨域请求发出而是禁止你“读取”跨域响应。很多情况下请求其实已经发出去了服务器也处理了但浏览器因为响应头里缺少允许跨域的声明把响应结果给拦截了。这就是为什么很多人发现后端日志里明明有请求记录前端却一直报错。1.3 哪些场景最容易触发跨域实际开发中以下场景几乎天天见前后端分离项目前端跑在 8080后端跑在 9090两套端口不同所有 Ajax 请求全是跨域。前端部署在 Nginx 上占了 80 端口后端服务在 8080通过域名访问页面后请求 API域名和端口都不同。本地开发时前端跑在http://localhost:5173后端跑在http://localhost:8080这算跨域因为端口不同。静态资源放在 CDN接口在自己的业务服务器上域名不同跨域。在这些场景下如果不做任何处理浏览器控制台会报错请求被拦截。接下来我们看看具体会出什么错、报错背后的机制是什么。2. 跨域问题具体会引发哪些故障2.1 前端报错背后的真实链路最常见的报错长这样Access to fetch at http://localhost:9090/api/users from origin http://localhost:8080 has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource.翻译一下就是浏览器在http://localhost:8080这个页面里向http://localhost:9090/api/users发请求被 CORS 策略拦截了因为响应头里没有Access-Control-Allow-Origin。这条报错信息已经说得很清楚了不是服务器没响应而是响应里缺少了跨域相关的响应头。浏览器的逻辑是响应我可以收到但没有跨域放行声明我就认为这个响应不安全不让你的脚本读取。后端此时如果打印日志大概率能看到请求已经进来接口正常执行完并返回了。问题出在浏览器这层安检。2.2 简单请求与非简单请求的区别跨域请求在浏览器里被分为两类简单请求Simple Request和非简单请求Preflighted Request。这个区分直接决定你配置跨域时要考虑哪些细节。简单请求需要同时满足两个条件请求方法是GET、POST、HEAD三种之一。请求头仅限于Accept、Accept-Language、Content-Language、Content-Type且 Content-Type 的值只能是application/x-www-form-urlencoded、multipart/form-data、text/plain中的一种。只要不满足其中一个条件就属于非简单请求。最常见的非简单请求场景是用application/json作为 Content-Type 发 POST 请求。对于非简单请求浏览器会先在正式请求之前额外发送一个OPTIONS方法请求这叫预检请求Preflight。预检请求的目的是问服务器我接下来要发的这个请求你允不允许服务器通过响应头告诉浏览器允许哪些方法、哪些头、哪些来源浏览器确认没问题之后才发真正的业务请求。这就解释了为什么有些接口在控制台 Network 面板里能看到两条请求一条OPTIONS一条POST。2.3 跨域导致的典型线上问题我见过不少线上事故表面现象五花八门追根溯源都是跨域前端页面加载出来了但列表数据一直是空的F12 一看全是 CORS 报错。登录接口在开发环境正常部署到测试环境就经常偶发失败检查后发现是预检请求没过被网关或权限过滤器拦了。项目从单体架构拆成微服务后前端调用多个服务每个服务都要单独配置跨域漏配一个接口就抓瞎。上传文件的功能突然不可用因为上传接口的 Content-Type 和自定义头触发了预检而服务器没有正确处理OPTIONS。我们接下来要解决的就是这些问题。3. 跨域问题的常规解决思路与选型3.1 JSONP只适用于 GET 的历史方案JSONPJSON with Padding是早期解决跨域的方案原理是利用script标签不受同源策略限制这一点动态创建一个 script 标签通过它的 src 属性向跨域服务器请求脚本服务器返回一段 JavaScript 代码里面包裹着业务数据。代码大概长这样script srchttp://api.example.com/getData?callbackhandleData/script script function handleData(data) { console.log(data); } /script看起来挺巧妙的但限制也很大只能用 GET 方法不能处理application/json等复杂请求而且安全性比较差比如响应内容被劫持后容易产生数据泄露风险。在现代前后端分离项目里JSONP 已经基本被 CORS 取代。面试时被问到能说清楚它为什么被淘汰即可不建议在项目里继续用它。3.2 CORS 的工作原理CORSCross-Origin Resource Sharing是 W3C 的标准方案核心是服务器在响应头里显式声明“我允许某些源来访问”。浏览器看到合法的跨域响应头就放行否则拦截。关键的响应头有这几个响应头作用Access-Control-Allow-Origin声明允许访问的源可以是具体源也可以是*Access-Control-Allow-Methods声明允许的 HTTP 方法Access-Control-Allow-Headers声明允许的请求头Access-Control-Allow-Credentials声明是否允许携带 Cookie 等凭证Access-Control-Max-Age预检请求的缓存时间浏览器端的请求头则是前端 Side 的事最常见的是Origin标识当前页面的源。服务器读取Origin之后按配置决定是否在响应里带上Access-Control-Allow-Origin。注意一个规则当Access-Control-Allow-Credentials为true时Access-Control-Allow-Origin不能设置为*因为浏览器不允许在携带凭证的场景下使用通配符源。这一点是配置跨域时特别容易踩的坑后面我详细讲。3.3 代理转发前后端分离项目的“余兴方案”除了让后端配置 CORS还有一种思路是让前端把请求发给同源的地址再由这个地址代理转发到目标服务器。这样一个房间里常见的做法开发阶段用 Vite/Webpack 的 proxy 配置把/api开头的请求转发到http://localhost:9090。生产环境用 Nginx 反向代理把页面请求和 API 请求放在同一个域下面。代理方案本质上是“让请求看起来是同源的”所以后端完全不用关心跨域问题。我在很多项目里用的是 Nginx 反代把前端静态资源和后端 API 归到同一个域名下既解决了跨域又能顺带做一层负载均衡。比如 Nginx 配置大致如下server { listen 80; server_name mysite.com; location / { root /opt/frontend; index index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这样前端页面在mysite.com接口也走mysite.com/api从浏览器视角看页面和接口同源跨域问题直接消失。3.4 三种方案如何选实际项目里我通常按这个原则选前后端分离后端能自由修改优先用 CORS 后端配置简单直接对前端最友好。前端项目不方便改后端配置或者后端是第三方服务优先用代理转发。老项目、纯静态页面、只能 GET 请求JSONP 可以作为应急方案但不要作为长期方案。大部分 Spring Boot 项目里CORS 是首选。我们下面就用一个实际项目来演示。4. Spring Boot 跨域配置实操含 MyBatis 项目示例4.1 示例工程结构和环境为了贴合实际我用一个 Spring Boot 2.7.x MyBatis 的项目来做演示核心是“用户列表查询接口”。前端跑在 5173 端口后端跑在 8080 端口目录结构如下demo ├── src/main/java/com/example/demo │ ├── config │ │ └── CorsConfig.java │ ├── controller │ │ └── UserController.java │ ├── entity │ │ └── User.java │ ├── mapper │ │ └── UserMapper.java │ └── DemoApplication.java ├── src/main/resources │ ├── mapper │ │ └── UserMapper.xml │ └── application.yml └── pom.xmlpom.xml里依赖很简单重点是 web、mybatis、mysqldependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependencyapplication.yml配置 MyBatis 的 mapper 路径和数据库连接server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true数据库表很简单create table user ( id bigint primary key auto_increment, username varchar(50) not null, email varchar(100), create_time datetime default current_timestamp );4.2 单接口配置CrossOrigin 注解如果只需要给某个接口开跨域最省事的办法是加CrossOrigin注解。比如 UserController 里的查询接口RestController RequestMapping(/api/users) public class UserController { private final UserMapper userMapper; public UserController(UserMapper userMapper) { this.userMapper userMapper; } CrossOrigin GetMapping(/list) public ListUser list() { return userMapper.findAll(); } }不加任何参数的CrossOrigin等价于允许所有来源、所有方法看起来简单但有个问题它默认不允许携带 Cookie。如果接口需要登录态得改成CrossOrigin(origins http://localhost:5173, allowCredentials true)这里origins指定了允许的来源allowCredentials允许携带 Cookie。注意一旦allowCredentials trueorigins就不能填*。单接口配置适合接口不多的情况接口多了之后每个 Controller 方法都要重复加注解维护成本高。我的习惯是接口特别少、临时联调用一下正式项目统一走全局配置。4.3 全局配置三个常用写法对比Spring Boot 里做全局跨域主流有三种写法实现WebMvcConfigurer、注册CorsFilterBean、通过CorsRegistration定制。其中前两种用得最多。第一种实现WebMvcConfigurer的addCorsMappingsConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }这段配置的意思是所有接口都允许跨域来源不限方法不限头不限允许携带 Cookie预检请求结果缓存 3600 秒。注意我用了allowedOriginPatterns而不是allowedOrigins。这两个看着像其实区别很大。allowedOrigins(*)在 Spring 内部会被直接设置为*一旦和allowCredentials(true)同时出现浏览器会拒绝而allowedOriginPatterns(*)会把每个实际请求的 Origin 动态填充到响应头里从而兼容携带凭证的情况。这是很多老版本项目升级之后突然跨域失败的原因之一我后面细讲。第二种注册CorsFilterConfiguration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }CorsFilter是基于 Servlet Filter 实现的优先级比WebMvcConfigurer的方式更高因为它不需要经过 Spring MVC 的 HandlerMapping直接作用在过滤器链上。第三种自定义CorsRegistration或在CorsConfiguration里做更细粒度的控制适用于不同接口不同规则的情况。比如/open/**放行所有源/user/**只允许固定源。这种我一般配合拦截器做项目复杂时更灵活。从优先级来说三种方式同时存在时CorsFilter的生效时机更早MyBatis 本身不参与这个过程因为跨域配置是 Web 层的职责和数据访问层没有直接关系。但很多人在一个项目里同时加注解、全局配置、过滤器最后发现配置互相覆盖一脸懵。我的建议是同一个项目里只保留一种 CORS 配置方式不要混用。4.4 与 MyBatis 数据访问结合的真实案例接下来把整个链路串起来。实体类Userpublic class User { private Long id; private String username; private String email; private LocalDateTime createTime; // getter/setter 省略 }Mapper 接口public interface UserMapper { ListUser findAll(); User findById(Long id); }Mapper XML?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.demo.mapper.UserMapper select idfindAll resultTypecom.example.demo.entity.User select id, username, email, create_time from user order by id /select select idfindById resultTypecom.example.demo.entity.User select id, username, email, create_time from user where id #{id} /select /mapperController 里加跨域配置并使用 MyBatis 查询RestController RequestMapping(/api/users) public class UserController { private final UserMapper userMapper; public UserController(UserMapper userMapper) { this.userMapper userMapper; } GetMapping(/list) public ListUser list() { return userMapper.findAll(); } GetMapping(/{id}) public User detail(PathVariable Long id) { return userMapper.findById(id); } }加上 4.3 小节的全局CorsConfig后启动项目后端在 8080 端口前端在 5173 端口跨域接口就能正常访问了。这里我想强调一点跨域配置不关心 MyBatis 怎么查数据它解决的只是“浏览器允不允许读取响应”的问题。数据返回成功与否那是 MyBatis 和数据库的职责。如果你用的是 PostgreSQL 或者别的数据库本文例子里的 SQL 需要做少量调整但跨域配置完全不用改。4.5 前端调用演示前端我用原生 fetch 和 axios 各写一份方便对照。原生 fetchfetch(http://localhost:8080/api/users/list, { credentials: include }) .then(response response.json()) .then(data { console.log(data); }) .catch(error { console.error(error); });用 axiosimport axios from axios; axios.get(http://localhost:8080/api/users/list, { withCredentials: true }) .then(response { console.log(response.data); }) .catch(error { console.error(error); });这里有几个容易忽略的细节浏览器里手动输入http://localhost:8080/api/users/list并不会触发跨域因为地址栏访问不是 JavaScript 发起的跨域请求。fetch 默认不会携带 Cookie如果要携带必须显式设置credentials: includeaxios 则要设置withCredentials: true。后端如果配置了allowCredentials(true)前端没带凭证配置请求虽然能通但 Cookie 不会带上登录态逻辑容易出问题。前面提到搜索热词里有“vue打包放进springboot中”这里顺便多说一句。如果前端打包后的静态文件直接放在 Spring Boot 的src/main/resources/static目录下然后和后端一起部署在同一个端口那么页面和 API 是同源的跨域问题天然不存在。这种方式适合小型项目或演示项目但正式项目一般还是前后端分开部署因为前端独立构建、独立扩容更方便。5. 面试中跨域问题的高频考点与答题套路5.1 面试官到底想考什么跨域问题几乎是 Java 后端面试的保留题尤其是涉及 Spring Boot 的岗位。面试官问这道题通常是想考察三个层次第一层知不知道跨域是什么能不能准确说出同源策略。第二层知不知道浏览器有预检机制能不能区分简单请求和非简单请求。第三层有没有真实处理过跨域问题能不能讲清楚 Spring Boot 里几种配置方案的区别以及碰到问题时怎么排查。所以光背概念不够得准备一个实际处理过的案例把你的排查思路、配置代码、踩坑过程讲出来。这一条在面试里的加分效果非常明显。5.2 高频题目与参考回答我整理了面试里出现频率最高的几道题每个都给一个简洁的参考回答方向。问题一什么是跨域为什么会有跨域问题回答要点浏览器的同源策略限制了不同源之间的脚本交互。协议、域名、端口任一不同就是跨域。浏览器为了安全默认不允许 JavaScript 读取跨域响应。问题二简单请求和非简单请求的区别回答要点简单请求不需要预检非简单请求需要先发 OPTIONS 预检。区分条件主要是请求方法是否限定在 GET/POST/HEADContent-Type 是否属于那三种指定类型以及是否有自定义请求头。问题三Spring Boot 如何处理跨域回答要点三种常用方式接口加CrossOrigin、实现WebMvcConfigurer的addCorsMappings方法、注册CorsFilterBean。说明各自的适用场景再补充origins和originPatterns的区别能加分。问题四如何判断后端配置是否生效回答要点看响应头里有没有Access-Control-Allow-Origin。如果请求是 OPTIONS还可以看响应里有没有Access-Control-Allow-Methods、Access-Control-Allow-Headers。用 curl 模拟跨域请求时要手动加Origin头curl -i -H Origin: http://localhost:5173 http://localhost:8080/api/users/list看返回的响应头是否包含Access-Control-Allow-Origin: http://localhost:5173。问题五跨域和 CSRF 攻击有关系吗这个问题偏延伸。正确理解是跨域限制是浏览器的一种安全手段但并不能完全防住 CSRF。因为某些简单请求如表单提交、图片请求仍然会发出。所以后端该做的时候还是要做 CSRF 防护比如校验 Token、SameSite Cookie 等。5.3 加分项说清楚 CORS 配置的优先级与失效场景面试中如果能顺口说出配置优先级往往会让面试官眼前一亮。我的理解是如果同时存在CrossOrigin注解和全局addCorsMappings配置Spring 会以注解上的属性为准因为注解级别的配置粒度更细。CorsFilter作为过滤器链上的一个环节会在请求进入 Spring MVC 之前就处理跨域所以它和基于 HandlerMapping 的全局配置在时间线上是不一样的。失效场景也值得提前准备接口返回了 4xx 或 5xx有些框架在异常处理里没有带上 CORS 响应头浏览器同样会拦截。网关层做了统一 CORS 配置但下游服务也做了两边都是*时可能没问题一旦一个配了allowCredentials(true)一个没配预检请求就会出现冲突。自定义过滤器先返回了响应没有进入跨域处理链响应里自然没有 CORS 头。后端重定向的响应里CORS 头没有透传也会导致拦截。这些内容不是背给面试官听就完事了实际项目里真的会遇到。下面的常见问题排查部分就是这些场景的详细展开。6. 跨域配置的常见坑与排查手册6.1 明明加了配置前端还是报跨域这是我被问过最多的情况后端配置了CorsConfig代码看起来没毛病前端就是不通。排查步骤我建议按顺序走第一步先用浏览器 Network 面板看请求有没有发出。如果是 0 请求或者直接红色拦截说明浏览器根本没收到的响应都来自服务器。第二步用 curl 模拟请求加上 Origin 头检查响应头curl -i -H Origin: http://localhost:5173 http://localhost:8080/api/users/list如果响应里没有Access-Control-Allow-Origin说明配置没生效。这时检查你的CorsConfig是否被组件扫描到了类上有没有Configuration是不是放在了启动类扫不到的包下。第三步检查是不是有多个配置类互相覆盖。我曾经遇到过一个项目A 同事在WebMvcConfigurer里配了跨域B 同事在另一个配置里也实现了WebMvcConfigurer后者没有调用addCorsMappings结果覆盖了前者的配置。Spring Boot 里多个WebMvcConfigurer是合并生效的但如果某个配置类里写了addCorsMappings但是条件不满足容易排查半天。还有一个常见原因是旧项目升级了 Spring Boot 版本allowedOrigins(*)配合allowCredentials(true)的方式在新版本里被浏览器拦截。这种问题正好对应了热词里提到的“springboot版本太高”我后面专门说。6.2 OPTIONS 请求返回 403/405预检请求被拒绝是最容易迷惑人的坑。现象是前端业务请求发出Network 面板里有个 OPTIONS 请求状态码是 403 或者 405但业务请求有时候又能通。原因多半是权限拦截器或安全框架把 OPTIONS 请求拦截了。比如你项目里有 Spring Security 或者自定义登录拦截器拦截规则通常是/**OPTIONS 请求没有带 Token直接被判断为未认证返回 401/403。解决办法是在过滤器或拦截器里放行 OPTIONS 请求Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } // 继续走登录校验 }如果是 Spring Security需要在配置里放行http.authorizeHttpRequests() .requestMatchers(HttpMethod.OPTIONS, /**).permitAll()注意CorsFilter的优先级如果不够高有可能在权限过滤器之后才执行。Spring Boot 里默认过滤器的注册顺序有讲究如果权限过滤器先执行它不放行 OPTIONSCorsFilter 根本没机会处理。这个问题的排查方向有两个一个是放行 OPTIONS另一个是调整过滤器顺序。6.3 携带 Cookie 时 allowCredentials 与 allowOrigin 的冲突这是配置里最隐蔽的坑。假如你写了config.addAllowedOrigin(*); config.setAllowCredentials(true);浏览器会直接报错提示Access-Control-Allow-Origin不能为*因为这里设置了Access-Control-Allow-Credentials为 true。语言层面的具体报错是The value of the Access-Control-Allow-Origin header in the response must not be the wildcard * when the requests credentials mode is include.解决办法就是用allowedOriginPatterns(*)或者明确写允许的来源config.addAllowedOriginPattern(*); config.setAllowCredentials(true);有些同学会问为什么不能是*因为浏览器认为携带 Cookie 的请求等于把用户凭证交给服务器这种情况下如果用通配符等于任何源都能拿到带凭证的响应安全隐患太大。所以浏览器强制要求响应头里的源必须精确匹配请求的源。6.4 自定义过滤器导致 CORS 失效项目里如果有自定义的过滤器并且过滤器的执行顺序在CorsFilter之前问题就来了。典型的场景是你写了一个过滤器专门处理响应头比如统一往响应里加时间戳、traceId或者做请求日志。如果这个过滤器里调用了response.getWriter()写了内容甚至直接返回了响应那么后边过滤器链上的 CORS 处理就没机会执行了。另外还有人喜欢在自定义过滤器里手动加 CORS 头但加漏了一个关键头比如只加了Access-Control-Allow-Origin没加Access-Control-Allow-Methods预检请求照样失败。我的经验是CORS 相关的逻辑统一交给CorsFilter或者 Spring MVC 的 CORS 处理机制不要在自定义过滤器里重复写。如果你需要在自定义过滤器里处理那就在最外层处理保证它能覆盖所有后续逻辑或者干脆在 Nginx 层解决。6.5 问题排查速查表我把实际项目中遇到的典型现象和排查方向整理成一张速查表方便你在工位上快速定位。现象可能原因排查方向请求被拦截Network 里只有一条请求响应缺少 Access-Control-Allow-Origin检查后端配置类是否生效、是否有多个配置覆盖Network 里 OPTIONS 请求 403/401权限拦截器拦截了预检请求在拦截器/安全配置中放行 OPTIONS带 Cookie 请求报错 Allow-Origin is*allowCredentials 与 allowOrigin 冲突改用 allowedOriginPatterns接口偶发跨域时好时坏预检请求被网关或负载均衡拦截检查网关层 CORS 配置与下游是否一致局部接口可以部分接口不行部分接口有注解覆盖全局配置检查 CrossOrigin 的 origins 配置升级 Spring Boot 后突然跨域报错版本升级导致 allowedOrigins 行为变化用 allowedOriginPatterns 替代生产环境正常本地开发跨域前端代理没配或者代理目标变了检查 Vite/Webpack 的 proxy 配置6.6 我的几条实操心得踩了几年跨域的坑有几个经验想单独分享。第一项目里配置跨域时优先使用allowedOriginPatterns而不是allowedOrigins。这不仅是为了兼容allowCredentials也是因为很多新版本的 Spring Boot 对allowedOrigins(*)做了严格校验提前用 pattern 写法能少踩很多升级坑。第二前端联调时让前端同事先看 Network 面板拿到实际响应的响应头再截图。很多时候问题不在后端配置而在前端请求头或者代理设置。后端配合给一张 curl 命令前端跑一下就能定位是浏览器层面拦截还是服务器没返回。第三不要迷信“全局配置了就能解决一切”。如果你项目里有网关Spring Cloud Gateway、Nginx网关层也做了跨域配置那后端服务层的 CORS 配置往往会产生重复头。重复头本身不致命但如果两边的源配置不一致预检请求就可能失败。线上环境我一般只在最外层网关配一次 CORS服务层全部关掉这样最干净。第四遇到不好排查的跨域问题不要花太多时间在浏览器控制台里读英文报错先把 Network 面板里的请求和响应头完整截图再对着速查表逐项对。90% 的跨域问题都能在响应头里找到答案。跨域本身不是复杂技术但它横跨前端、后端、网关、浏览器几个层面任何一个环节漏了都会让联调变得痛苦。这篇文章里的所有代码和配置都来自我实际使用过的方案希望能帮你少走弯路。尤其是 Spring Boot 版本升级之后突然出现的跨域异常八成是 CORS 配置写法的问题把allowedOrigins换成allowedOriginPatterns往往立竿见影。
返回列表