跳到主要内容前后端跨域处理全指南:Java 后端+Vue 前端完整解决方案 | 极客日志Java大前端java
前后端跨域处理全指南:Java 后端+Vue 前端完整解决方案
跨域问题成因及浏览器同源策略,提供 Java 后端与 Vue 前端的六种跨域解决方案。包括 @CrossOrigin 注解、全局 CORS 配置、过滤器、Spring Security 集成、Vue 开发代理及 Nginx 反向代理。涵盖代码示例、优缺点分析及最佳实践建议,解决生产环境部署中的常见问题如预检请求失败、Cookie 携带等,适用于初中级开发者参考。
修罗36 浏览 前后端跨域处理全指南:Java 后端+Vue 前端完整解决方案
一、跨域基础概念
1.1 什么是跨域?
跨域(Cross-Origin)是指浏览器出于安全考虑,限制从一个域(协议 + 域名 + 端口)加载的网页去请求另一个域的资源。当协议、域名或端口三者中有任意一项不同时,就会触发跨域限制。
示例:
http://localhost:3000 → http://localhost:8080(端口不同)❌ 跨域
http://example.com → https://example.com(协议不同)❌ 跨域
http://api.example.com → http://www.example.com(子域名不同)❌ 跨域
http://localhost:3000 → http://localhost:3000/api(同源)✅ 不跨域
1.2 为什么会有跨域限制?
浏览器的同源策略(Same-Origin Policy,SOP) 是 Web 安全的核心机制,用于:
- 防止恶意网站读取另一个网站的敏感数据(如 Cookie、localStorage)
- 防止 CSRF(跨站请求伪造)攻击
- 保护用户隐私和数据安全
注意:跨域限制仅存在于浏览器,服务器之间的 HTTP 请求(如 Java HttpClient、Postman)不受此限制。
1.3 CORS(跨域资源共享)工作原理
CORS 是 W3C 标准,通过服务器设置 HTTP 响应头告知浏览器允许哪些源访问资源。
简单请求流程:
浏览器请求 → 服务器返回 Access-Control-Allow-Origin → 浏览器验证放行
复杂请求(预检请求)流程:
浏览器 OPTIONS 预检 → 服务器返回允许方法/头 → 浏览器发送真实请求 → 返回数据
二、完整的跨域处理方案列表
以下是适用于 Java 后端+Vue 前端的 6 种核心方案:
| 方案 | 适用场景 | 实现复杂度 | 安全性 | 需要后端配合 |
|---|
| 1. @CrossOrigin 注解 | 少量接口快速测试 | ⭐ 低 | 中 | ✅ 是 |
| 2. 全局 CORS 配置 | 企业级标准方案 | ⭐⭐ 中 | 高 | ✅ 是 |
| 3. 过滤器 Filter | 动态权限控制 | ⭐⭐ 中 | 高 | ✅ 是 |
| 4. Spring Security 集成 | 带权限系统的项目 | ⭐⭐⭐ 高 | 高 | ✅ 是 |
| 5. Vue DevServer 代理 | 开发环境专用 | ⭐ 低 |
| 6. Nginx 反向代理 | 生产环境部署 | ⭐⭐ 中 | 高 | ❌ 否 |
三、方案详解
方案 1:@CrossOrigin 注解(最简单)
3.1.1 实现原理
通过在 Controller 或方法上添加 @CrossOrigin 注解,Spring MVC 会自动添加 CORS 响应头。
3.1.2 后端实现
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@CrossOrigin(origins = "http://localhost:8080")
@GetMapping("/api/demo")
public String demo() {
return "Hello from CORS!";
}
}
@RestController
@RequestMapping("/api")
@CrossOrigin(
origins = {"http://localhost:8080", "http://localhost:3000"},
methods = {RequestMethod.GET, RequestMethod.POST},
maxAge = 3600
)
public class ApiController {
@GetMapping("/users")
public String getUsers() {
return "Users data";
}
@PostMapping("/login")
public String login() {
return "Login success";
}
}
3.1.3 前端配置(Vue + Axios)
import axios from 'axios';
const request = axios.create({
baseURL: 'http://localhost:8080',
timeout: 5000
});
export default request;
<!-- src/components/Demo.vue -->
<template>
<div>{{ message }}</div>
</template>
<script>
import request from '@/api/request';
export default {
data() {
return {
message: ''
};
},
async mounted() {
try {
const res = await request.get('/api/demo');
this.message = res.data;
} catch (error) {
console.error('跨域请求失败:', error);
}
}
};
</script>
3.1.4 优缺点分析
- 配置简单,一行注解搞定
- 适合少量接口或快速原型开发
- 每个接口都需要单独配置,维护成本高
- 不适合大规模企业项目
- 无法统一管理跨域规则
方案 2:全局 CORS 配置(推荐)
3.2.1 实现原理
实现 WebMvcConfigurer 接口,重写 addCorsMappings 方法,统一配置全局跨域规则。
3.2.2 后端实现
package com.example.config;
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:8080",
"http://localhost:3000",
"https://www.example.com"
)
.allowedMethods(
"GET", "POST", "PUT", "DELETE", "OPTIONS"
)
.allowedHeaders("*")
.exposedHeaders("X-Total-Count", "X-Page-Count")
.allowCredentials(true)
.maxAge(3600);
}
}
3.2.3 前端配置
import axios from 'axios';
const service = axios.create({
baseURL: 'http://localhost:8080/api',
timeout: 10000,
withCredentials: true
});
service.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
}, error => {
return Promise.reject(error);
});
service.interceptors.response.use(response => response, error => {
if (error.response) {
switch (error.response.status) {
case 401:
console.error('未授权,请登录');
break;
case 403:
console.error('拒绝访问');
break;
case 500:
console.error('服务器错误');
break;
}
}
return Promise.reject(error);
});
export default service;
import request from '@/utils/request';
export const getUserList = (params) => {
return request({
url: '/users',
method: 'get',
params
});
};
export const createUser = (data) => {
return request({
url: '/users',
method: 'post',
data
});
};
3.2.4 优缺点分析
- 统一管理,便于维护
- 一次配置,全局生效
- 支持精细化的权限控制
方案 3:过滤器 Filter(灵活控制)
3.3.1 实现原理
通过自定义 Filter,在请求链中手动设置 CORS 响应头,可实现动态的跨域逻辑。
3.3.2 后端实现
package com.example.filter;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String origin = request.getHeader("Origin");
if (isAllowedOrigin(origin)) {
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Max-Age", "3600");
}
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
filterChain.doFilter(request, response);
}
private boolean isAllowedOrigin(String origin) {
if (origin == null) {
return false;
}
String[] allowedOrigins = {"http://localhost:8080", "http://localhost:3000", "https://www.example.com"};
for (String allowed : allowedOrigins) {
if (allowed.equals(origin)) {
return true;
}
}
return false;
}
}
3.3.3 前端配置
3.3.4 优缺点分析
- 灵活性最高,可实现动态跨域控制
- 可根据请求参数、用户角色等动态判断是否允许跨域
- 适用于多租户、SAAS 系统等复杂场景
- 实现相对复杂
- 需要手动维护 CORS 响应头逻辑
- 性能略低于 Spring 内置 CORS 支持
方案 4:Spring Security 集成(带权限系统)
3.4.1 实现原理
在 Spring Security 安全框架中集成 CORS,确保跨域请求在经过安全链时正确处理。
3.4.2 后端实现
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated());
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList(
"http://localhost:8080",
"http://localhost:3000",
"https://www.example.com"
));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setExposedHeaders(Arrays.asList("X-Total-Count", "Authorization"));
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
3.4.3 前端配置
import axios from 'axios';
import { Message } from 'element-ui';
const service = axios.create({
baseURL: 'http://localhost:8080',
timeout: 10000,
withCredentials: true
});
service.interceptors.request.use(config => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
}, error => {
return Promise.reject(error);
});
service.interceptors.response.use(response => {
const res = response.data;
if (res.code !== 200) {
Message.error(res.message || '请求失败');
if (res.code === 401) {
localStorage.removeItem('access_token');
window.location.href = '/login';
}
return Promise.reject(new Error(res.message || 'Error'));
} else {
return res;
}
}, error => {
console.error('请求错误:', error);
if (error.response && error.response.status === 401) {
Message.error('未授权,请重新登录');
localStorage.removeItem('access_token');
window.location.href = '/login';
}
return Promise.reject(error);
});
export default service;
import request from '@/utils/request';
export const login = (data) => {
return request({
url: '/api/auth/login',
method: 'post',
data
});
};
export const getUserInfo = () => {
return request({
url: '/api/user/info',
method: 'get'
});
};
3.4.4 优缺点分析
- 完美集成 Spring Security 安全体系
- 支持 Token 认证、Session 认证等多种认证方式
- 适用于需要权限控制的复杂业务系统
- 配置复杂度较高
- 需要理解 Spring Security 的工作原理
- 新手容易出错
方案 5:Vue DevServer 代理(开发环境)
3.5.1 实现原理
利用 Webpack DevServer 内置的 http-proxy-middleware,在开发服务器层面对请求进行转发,绕过浏览器的同源限制。
3.5.2 前端配置
vue.config.js 配置(Vue CLI 项目):
module.exports = {
devServer: {
port: 8080,
host: '0.0.0.0',
open: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': ''
},
headers: {
'X-Dev-Proxy': 'true'
}
},
'/upload': {
target: 'http://localhost:9090',
changeOrigin: true,
pathRewrite: {
'^/upload': '/files'
}
}
}
}
};
vite.config.js 配置(Vite 项目):
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
3.5.3 Axios 配置
import axios from 'axios';
const service = axios.create({
baseURL: process.env.NODE_ENV === 'development' ? '/api' : 'http://api.example.com',
timeout: 10000
});
export default service;
环境变量配置(.env.development):
NODE_ENV=development VUE_APP_BASE_API=/api
NODE_ENV=production VUE_APP_BASE_API=https://api.example.com
3.5.4 优缺点分析
- 开发环境最简单、最快捷的方案
- 无需后端配合,前端独立完成
- 支持 WebSocket 代理
- 仅适用于开发环境,生产环境不可用
- 生产部署时需要修改 baseURL 或使用其他方案
- 调试时代理转发可能影响问题定位
方案 6:Nginx 反向代理(生产环境)
3.6.1 实现原理
通过 Nginx 作为反向代理服务器,前端请求 Nginx 同源地址,Nginx 转发到后端。对浏览器而言是同源请求,完美解决跨域问题。
3.6.2 Nginx 配置
server {
listen 80;
server_name www.example.com;
# 前端静态资源
location / {
root /usr/share/nginx/html; # Vue 打包后的 dist 目录
index index.html;
try_files $uri $uri/ /index.html; # 解决 Vue 路由刷新 404
}
# 后端 API 代理
location /api/ {
# 转发到后端真实地址
proxy_pass http://localhost:8080/;
# 关键:传递原始 Host 和客户端 IP
proxy_set_header Host $proxy_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
完整 CORS 配置(后端不配置 CORS 时使用):
server {
listen 80;
server_name api.example.com;
location / {
# 允许的源(生产环境指定具体域名)
add_header 'Access-Control-Allow-Origin' 'https://www.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Max-Age' '3600' always;
# 处理 OPTIONS 预检请求
if ($request_method = OPTIONS) {
return 204;
}
# 反向代理到后端
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
listen 80;
server_name api.example.com;
# 动态设置允许的源
set $cors_origin "";
if ($http_origin ~* ^(https?://(localhost|www\.example\.com)(:[0-9]+)?$)) {
set $cors_origin $http_origin;
}
location / {
# 使用变量设置允许的源
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
if ($request_method = OPTIONS) {
return 204;
}
proxy_pass http://localhost:8080;
}
}
3.6.3 前端配置
import axios from 'axios';
const service = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? '/api'
: 'http://localhost:8080',
timeout: 10000,
withCredentials: true
});
export default service;
3.6.4 优缺点分析
- 生产环境最推荐方案
- 前后端同域,天然避免跨域问题
- 支持负载均衡、缓存、HTTPS 等高级功能
- 无需修改后端代码
- 需要额外的 Nginx 服务器
- 配置相对复杂
- 需要运维知识
四、最佳实践建议
4.1 开发环境
推荐方案:Vue DevServer 代理 + 后端 CORS 全局配置
- 前端独立开发,不依赖后端配置
- 快速迭代,方便调试
- 为生产环境做好准备
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
};
4.2 生产环境
推荐方案:Nginx 反向代理 + 后端 CORS 全局配置(双重保障)
- Nginx 作为流量入口,统一处理跨域
- 后端 CORS 作为备用,应对直接访问后端的情况
- 支持多环境部署(开发、测试、生产)
浏览器 ↓ Nginx (80/443)
├─ / → 前端静态资源 (同域)
└─ /api → 后端 API (同域代理)
↓ Spring Boot (8080)
4.3 复杂权限系统
推荐方案:Spring Security + CORS + Token 认证
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated());
return http.build();
}
service.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
});
4.4 多租户/SAAS 系统
- 根据租户域名动态设置允许的源
- 支持租户级别的跨域白名单管理
- 从数据库或配置中心读取跨域规则
private boolean isAllowedOrigin(String origin, String tenantId) {
List<String> allowedOrigins = tenantService.getCorsWhiteList(tenantId);
return allowedOrigins.contains(origin);
}
五、常见问题排查
5.1 配置失败的典型原因
- 后端未配置 CORS
- Nginx 未添加跨域头
- OPTIONS 预检请求失败
- 打开浏览器开发者工具 → Network
- 查看请求的 Response Headers
- 检查是否有
Access-Control-Allow-Origin 字段
- 检查 OPTIONS 请求是否返回 204 或 200
问题 2:Credential is not supported for wildcard origin
- 同时设置了
allowCredentials=true 和 allowedOrigins="*"
- 浏览器不允许通配符源携带 Cookie
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList("*"));
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList("http://localhost:8080"));
问题 3:OPTIONS 预检请求返回 404/403
- 后端未实现 OPTIONS 接口
- Security 拦截了 OPTIONS 请求
- Nginx 未处理预检请求
http.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll());
# Nginx 处理 OPTIONS
if ($request_method = OPTIONS) {
return 204;
}
5.2 Nginx 配置踩坑
坑 1:proxy_pass 的斜杠问题
# 错误:路径会保留/api
location /api/ {
proxy_pass http://localhost:8080; # 请求 /api/user → http://localhost:8080/api/user
}
# 正确:去掉/api 前缀
location /api/ {
proxy_pass http://localhost:8080/; # 请求 /api/user → http://localhost:8080/user
}
坑 2:缺少 always 参数
# 错误:错误响应时不会添加跨域头
add_header 'Access-Control-Allow-Origin' 'http://localhost:8080';
# 正确:所有响应都添加跨域头
add_header 'Access-Control-Allow-Origin' 'http://localhost:8080' always;
5.3 Cookie 携带问题
现象:请求未携带 Cookie 或 Set-Cookie 不生效
- 前端未设置
withCredentials: true
- 后端未设置
allowCredentials: true
- Cookie 的
SameSite 属性设置不当
axios.get('/api/user', { withCredentials: true });
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList("http://localhost:8080"));
Cookie cookie = new Cookie("token", tokenValue);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
cookie.setAttribute("SameSite", "None");
response.addCookie(cookie);
5.4 调试技巧
技巧 1:使用 curl 测试 CORS
curl -X OPTIONS -H "Origin: http://localhost:8080" \
-H "Access-Control-Request-Method: GET" \
-v http://localhost:8080/api/user
curl -I http://localhost:8080/api/user
技巧 2:浏览器 Network 面板分析
- 打开开发者工具 → Network
- 勾选"Preserve log"
- 发送跨域请求
- 查看请求详情:
- Request Headers:检查 Origin、Access-Control-Request-Method
- Response Headers:检查 Access-Control-Allow-*
- Console:查看具体错误信息
技巧 3:临时禁用浏览器安全策略(仅用于调试)
chrome.exe --disable-web-security --user-data-dir="C:/temp/chrome-dev"
六、总结
6.1 方案对比表
| 方案 | 适用场景 | 推荐度 | 生产可用性 |
|---|
| @CrossOrigin 注解 | 少量接口测试 | ⭐⭐ | ✅ 是 |
| 全局 CORS 配置 | 企业级标准方案 | ⭐⭐⭐⭐⭐ | ✅ 是 |
| 过滤器 Filter | 动态权限控制 | ⭐⭐⭐⭐ | ✅ 是 |
| Spring Security 集成 | 带权限系统 | ⭐⭐⭐⭐⭐ | ✅ 是 |
| Vue DevServer 代理 | 开发环境 | ⭐⭐⭐⭐⭐ | ❌ 否 |
| Nginx 反向代理 | 生产环境 | ⭐⭐⭐⭐⭐ | ✅ 是 |
6.2 核心要点
- 跨域是浏览器的安全机制,服务器之间无此限制
- CORS 是最标准的解决方案,推荐后端配置
- 开发环境用代理,生产环境用 Nginx
- allowCredentials=true 时,allowedOrigins 不能是"*"
- Spring Security 需放行 OPTIONS 预检请求
- Nginx 配置注意斜杠和 always 参数
- 携带 Cookie 需前后端同时配置
6.3 推荐架构
开发环境:Vue DevServer 代理 + 后端 CORS 全局配置
生产环境:Nginx 反向代理 + 后端 CORS 全局配置(双重保障)
权限系统:Spring Security + Token 认证
参考资料
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online