【浏览器CORS问题解决方案】SpringBoot+Vue3前后端全覆盖:浏览器跨域问题的多样化解决方案
简言:
本文聚焦前后端分离开发中常见的浏览器跨域问题,以 SpringBoot 后端和 Vue3 前端为核心场景,系统梳理了 5 种解决方案:后端涵盖单个接口 @CrossOrigin 注解、全局配置类(推荐)、过滤器 Filter 配置 3 种核心方案,前端包含 Vite 代理配置(开发环境专用)、Axios 请求拦截器(配合后端,生产环境可用)2 种实用方案。每种方案均搭配详细实操步骤、可直接复用的代码及清晰流程图,同时提供场景选型指南与常见问题排查技巧,助力读者快速理解跨域原理并根据实际需求落地解决方案。
文章目录
- 一、先搞懂:什么是跨域?为什么会被拦截?
- 二、后端SpringBoot解决跨域:3种核心方案
- 方案1:最简洁——单个接口加@CrossOrigin注解
- 方案2:最常用——全局配置类(推荐)
- 方案3:最灵活——过滤器Filter配置
- 三、前端Vue3解决跨域:2种实用方案
- 方案1:开发环境专用——Vite代理配置(最常用)
- 方案2:生产环境可用——Axios请求拦截器(配合后端)
- 四、总结:不同场景怎么选方案?
- 五、常见问题排查
一、先搞懂:什么是跨域?为什么会被拦截?
简单说,当浏览器的请求地址(比如前端Vue项目的http://localhost:5173)和服务器接口地址(比如后端SpringBoot项目的http://localhost:8080)满足“协议、域名、端口”三者中任意一个不同时,就属于跨域请求。
那为什么浏览器要拦截呢?这是浏览器的“同源策略”在起保护作用,防止恶意网站窃取其他网站的敏感数据。但在合法的前后端分离开发中,我们就需要主动“破解”这个限制,这就是所谓的“跨域解决方案”。
小技巧:判断是否跨域的简单方法——看浏览器地址栏的地址和接口请求地址的“协议://域名:端口”是不是完全一样,不一样就是跨域。
先搭个测试环境,后面所有方案都基于这个环境验证:
- 前端Vue3项目:运行地址 http://localhost:5173(Vite默认端口)
- 后端SpringBoot项目:运行地址 http://localhost:8080(默认端口)
- 测试接口:后端写一个简单的接口 /api/hello,返回“Hello 跨域测试”
未解决跨域时,前端调用接口会出现如下错误(F12打开控制台查看):

二、后端SpringBoot解决跨域:3种核心方案
后端解决跨域是最推荐的方式,因为只需要配置一次,所有前端请求都能生效,不用前端做额外处理。
方案1:最简洁——单个接口加@CrossOrigin注解
适用场景:需要跨域的接口比较少,比如只开放一两个接口给前端。
操作步骤:直接在后端的Controller接口方法上添加@CrossOrigin注解即可。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.CrossOrigin;@RestController
@RequestMapping("/api")
public class HelloController {// 加上这个注解,允许来自http://localhost:5173的跨域请求@CrossOrigin(origins = "http://localhost:5173")@GetMapping("/hello")public String hello() {return "Hello 跨域测试";}
}
参数说明:origins指定允许跨域的前端地址,多个地址可以用逗号分隔,比如@CrossOrigin(origins = {“http://localhost:5173”, "http://www.test.com"});如果想允许所有地址跨域,可以写@CrossOrigin;如果想允许所有地址跨域,可以写@CrossOrigin)(origins = “*”)(生产环境不推荐,有安全风险)。
测试效果:前端调用接口,控制台无错误,能正常拿到返回值。

方案2:最常用——全局配置类(推荐)
适用场景:整个项目的所有接口都需要跨域,比如前后端分离的正式项目。
操作步骤:新建一个配置类,实现WebMvcConfigurer接口,重写addCorsMappings方法。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration // 标识这是一个配置类
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**") // 对所有接口生效.allowedOrigins("http://localhost:5173") // 允许的前端地址.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法.allowedHeaders("*") // 允许的请求头.allowCredentials(true) // 是否允许携带Cookie(重要,前端传参如果需要Cookie必须设为true).maxAge(3600); // 预检请求的有效期,单位秒(3600表示1小时内不用重复发预检请求)}
}
关键说明:
- addMapping("/“):表示对项目中所有接口都启用跨域配置,也可以指定特定路径,比如”/api/"只对/api开头的接口生效。
- allowCredentials(true):如果前端需要传递Cookie(比如登录后的身份验证),这个参数必须设为true,同时allowedOrigins不能写"*",必须指定具体的前端地址。
- maxAge(3600):浏览器发送跨域请求前,会先发送一个OPTIONS预检请求,验证是否允许跨域,这个参数设置预检请求的有效期,有效期内不用重复发送,提高性能。
方案3:最灵活——过滤器Filter配置
适用场景:需要更精细的跨域控制,比如根据不同的路径设置不同的跨域规则。
操作步骤:新建一个过滤器类,实现Filter接口,在doFilter方法中设置跨域响应头。
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@WebFilter(filterName = "corsFilter", urlPatterns = "/*") // 拦截所有请求
public class CorsFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletResponse res = (HttpServletResponse) response;// 允许的前端地址res.setHeader("Access-Control-Allow-Origin", "http://localhost:5173");// 允许的请求方法res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");// 允许的请求头res.setHeader("Access-Control-Allow-Headers", "*");// 允许携带Cookieres.setHeader("Access-Control-Allow-Credentials", "true");// 预检请求有效期res.setHeader("Access-Control-Max-Age", "3600");// 如果是预检请求,直接返回200if ("OPTIONS".equals(((HttpServletRequest) request).getMethod())) {res.setStatus(HttpServletResponse.SC_OK);return;}// 继续执行后续过滤器chain.doFilter(request, response);}
}
然后在SpringBoot的启动类上添加@ServletComponentScan注解,扫描过滤器:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;@SpringBootApplication
@ServletComponentScan // 扫描Servlet相关组件(包括过滤器)
public class CorsDemoApplication {public static void main(String[] args) {SpringApplication.run(CorsDemoApplication.class, args);}
}
三、前端Vue3解决跨域:2种实用方案
前端解决跨域的核心思路是“代理”,即通过前端项目本身作为中间代理,把请求转发给后端,因为代理服务器和前端是“同源”的,就不会触发跨域限制。
方案1:开发环境专用——Vite代理配置(最常用)
适用场景:Vue3项目用Vite构建的开发环境(生产环境建议用后端配置或Nginx代理)。
操作步骤:修改前端项目根目录下的vite.config.js文件,添加server.proxy配置。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue()],server: {proxy: {// 1. 配置代理规则:以/api开头的请求都转发到目标地址'/api': {target: 'http://localhost:8080', // 后端接口的真实地址changeOrigin: true, // 开启代理,会把请求头中的Origin改成目标地址rewrite: (path) => path.replace(/^\/api/, '') // 重写路径(可选,根据接口实际路径调整)}}}
})
举个例子理解rewrite:如果后端接口实际是http://localhost:8080/hello,而前端想写/api/hello来调用,就需要rewrite把/api去掉,这样转发后就是正确的地址;如果后端接口本身就带/api前缀(比如http://localhost:8080/api/hello),就不需要rewrite。
前端调用接口示例(用Axios,需先安装:npm install axios):
// 在src/utils/request.js中配置Axios
import axios from 'axios'const request = axios.create({baseURL: '', // 因为配置了代理,这里可以空着或写 '/'timeout: 5000
})export default request// 在组件中调用
import request from '@/utils/request'async getHello() {try {const res = await request.get('/api/hello') // 这里的/api会被代理转发到后端console.log(res.data) // 输出"Hello 跨域测试"} catch (err) {console.error(err)}
}
测试效果:前端调用接口时,浏览器控制台的请求地址显示http://localhost:5173/api/hello(前端地址),但实际已经转发到http://localhost:8080/hello,不会出现跨域错误。
方案2:生产环境可用——Axios请求拦截器(配合后端)
适用场景:需要在请求头中添加特殊信息(比如Token),同时解决跨域,本质还是需要后端配合允许对应的请求头。
操作步骤:在Axios的请求拦截器中添加跨域相关的请求头,后端需要允许这些头(对应后端方案2中的allowedHeaders=“*”)。
import axios from 'axios'const request = axios.create({baseURL: 'http://localhost:8080', // 直接写后端真实地址timeout: 5000
})// 请求拦截器
request.interceptors.request.use((config) => {// 添加跨域相关的请求头(后端需允许这些头)config.headers['Access-Control-Allow-Origin'] = 'http://localhost:5173'// 可以顺便添加Token等其他头信息// config.headers['Token'] = localStorage.getItem('token')return config},(error) => {return Promise.reject(error)}
)export default request// 组件中调用
async getHello() {try {const res = await request.get('/api/hello')console.log(res.data)} catch (err) {console.error(err)}
}
注意:这种方式单独用不能解决跨域,必须配合后端的跨域配置(比如后端用方案2的全局配置),因为请求头的允许需要后端来设置。
四、总结:不同场景怎么选方案?
| 场景 | 推荐方案 | 优点 |
|---|---|---|
| 单个/少数接口跨域 | SpringBoot @CrossOrigin注解 | 简单快捷,不用额外配置类 |
| 整个项目接口跨域(开发+生产) | SpringBoot全局配置类 | 一次配置全局生效,灵活可控 |
| Vue3开发环境跨域(Vite构建) | Vite代理配置 | 不依赖后端,开发调试方便 |
| 生产环境跨域(大型项目) | 后端全局配置 + Nginx代理 | 性能好,安全可靠(Nginx代理后续可单独讲) |
五、常见问题排查
- 问题1:配置后还是跨域?——检查后端allowedOrigins是否写对前端地址,前端代理的target是否写对后端地址;如果带Cookie,后端allowCredentials是否为true,且allowedOrigins不能是"*"。
- 问题2:预检请求报错?——后端是否允许OPTIONS方法(方案2和3都已包含,方案1需要注意)。
- 问题3:前端能请求但拿不到数据?——检查接口路径是否正确,比如代理的rewrite是否配置正确,后端接口是否返回了正确的数据。
按照上面的方案操作,基本能解决99%的SpringBoot+Vue3跨域问题。如果还有特殊场景,可以留言讨论~
