【浏览器CORS问题解决方案】SpringBoot+Vue3前后端全覆盖:浏览器跨域问题的多样化解决方案

简言:

本文聚焦前后端分离开发中常见的浏览器跨域问题,以 SpringBoot 后端和 Vue3 前端为核心场景,系统梳理了 5 种解决方案:后端涵盖单个接口 @CrossOrigin 注解、全局配置类(推荐)、过滤器 Filter 配置 3 种核心方案,前端包含 Vite 代理配置(开发环境专用)、Axios 请求拦截器(配合后端,生产环境可用)2 种实用方案。每种方案均搭配详细实操步骤、可直接复用的代码及清晰流程图,同时提供场景选型指南与常见问题排查技巧,助力读者快速理解跨域原理并根据实际需求落地解决方案。

文章目录

一、先搞懂:什么是跨域?为什么会被拦截?

简单说,当浏览器的请求地址(比如前端Vue项目的http://localhost:5173)和服务器接口地址(比如后端SpringBoot项目的http://localhost:8080)满足"协议、域名、端口"三者中任意一个不同时,就属于跨域请求。

那为什么浏览器要拦截呢?这是浏览器的"同源策略"在起保护作用,防止恶意网站窃取其他网站的敏感数据。但在合法的前后端分离开发中,我们就需要主动"破解"这个限制,这就是所谓的"跨域解决方案"。

小技巧:判断是否跨域的简单方法------看浏览器地址栏的地址和接口请求地址的"协议://域名:端口"是不是完全一样,不一样就是跨域。

先搭个测试环境,后面所有方案都基于这个环境验证:

未解决跨域时,前端调用接口会出现如下错误(F12打开控制台查看):

二、后端SpringBoot解决跨域:3种核心方案

后端解决跨域是最推荐的方式,因为只需要配置一次,所有前端请求都能生效,不用前端做额外处理。

方案1:最简洁------单个接口加@CrossOrigin注解

适用场景:需要跨域的接口比较少,比如只开放一两个接口给前端。

操作步骤:直接在后端的Controller接口方法上添加@CrossOrigin注解即可。

java 复制代码
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方法。

java 复制代码
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 {

    @Override
    public 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方法中设置跨域响应头。

java 复制代码
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 {

    @Override
    public 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", "*");
        // 允许携带Cookie
        res.setHeader("Access-Control-Allow-Credentials", "true");
        // 预检请求有效期
        res.setHeader("Access-Control-Max-Age", "3600");

        // 如果是预检请求,直接返回200
        if ("OPTIONS".equals(((HttpServletRequest) request).getMethod())) {
            res.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        // 继续执行后续过滤器
        chain.doFilter(request, response);
    }
}

然后在SpringBoot的启动类上添加@ServletComponentScan注解,扫描过滤器:

java 复制代码
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配置。

javascript 复制代码
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):

javascript 复制代码
// 在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="*")。

javascript 复制代码
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跨域问题。如果还有特殊场景,可以留言讨论~

相关推荐
Mos_x4 小时前
【Spring Boot】Spring Boot解决循环依赖
java·spring boot·spring
亚林瓜子4 小时前
AWS Elastic Beanstalk中安装tesseract5.3.4版本
spring boot·ocr·tesseract·aws·beanstalk·tess4j·eb
QuantumLeap丶5 小时前
《uni-app跨平台开发完全指南》- 04 - 页面布局与样式基础
vue.js·微信小程序·uni-app
掘金安东尼5 小时前
GPT-6 会带来科学革命?奥特曼最新设想:AI CEO、便宜医疗与全新计算机
前端·vue.js·github
洞窝技术5 小时前
前端人必看的 node_modules 瘦身秘籍:从臃肿到轻盈,Umi 项目依赖优化实战
前端·vue.js·react.js
啷咯哩咯啷6 小时前
el-table-v2 实现自适应列宽
前端·javascript·vue.js
景早7 小时前
小黑记账清单案例(axios,echarts,vue)
前端·vue.js·echarts
檐下翻书1737 小时前
Spring Boot 深度剖析:从虚拟线程到声明式 HTTP 客户端,再到云原生最优解
spring boot·http·云原生
Java 码农7 小时前
vue 使用vueCli 搭建vue2.x开发环境,并且指定ts 和less
前端·vue.js·less