跨域问题解决方案指南
什么是跨域(CORS)?
跨域资源共享(CORS,Cross-Origin Resource Sharing)是浏览器的一种安全机制。当前端应用尝试访问不同域名、端口或协议的 API 时,浏览器会阻止这种请求。
跨域场景示例:
前端:http://localhost:5173
后端:https://juleon.site/api
结果:❌ 跨域错误
解决方案
方案一:开发环境使用 Vite 代理(推荐)
在开发环境中,使用 Vite 的代理功能将请求转发到后端服务器。
1. 配置 vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
plugins: [vue()],
server: {
port: 5173,
// 配置代理
proxy: {
// 方式一:简单代理
'/api': {
target: env.VITE_API_BASE_URL || 'https://juleon.site',
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, '') // 如果需要去掉 /api 前缀
},
// 方式二:多个代理
'/api/v1': {
target: 'https://api1.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, '')
},
'/api/v2': {
target: 'https://api2.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v2/, '')
}
}
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
}
})
2. 配置 axios baseURL
// src/api/request.ts
import axios from 'axios'
const request = axios.create({
// 开发环境使用相对路径,会被代理转发
// 生产环境使用完整 URL
baseURL: import.meta.env.DEV ? '/api' : import.meta.env.VITE_API_BASE_URL,
timeout: 60000,
headers: {
'Content-Type': 'application/json'
}
})
export default request
3. 环境变量配置
# .env.development
VITE_API_BASE_URL=https://juleon.site/api
# .env.production
VITE_API_BASE_URL=https://juleon.site/api
4. 请求示例
// 前端代码
import request from '@/api/request'
// 实际请求:http://localhost:5173/api/users
// 代理转发到:https://juleon.site/api/users
request.get('/users')
方案二:后端配置 CORS(生产环境推荐)
如果你有后端控制权,可以在后端配置 CORS 响应头。
Node.js (Express) 示例
const express = require('express')
const cors = require('cors')
const app = express()
// 方式一:允许所有来源(不推荐用于生产环境)
app.use(cors())
// 方式二:指定允许的来源(推荐)
app.use(cors({
origin: [
'http://localhost:5173',
'https://yourdomain.com'
],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}))
// 或者手动设置响应头
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:5173')
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.header('Access-Control-Allow-Credentials', 'true')
if (req.method === 'OPTIONS') {
return res.sendStatus(200)
}
next()
})
Spring Boot 示例
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:5173", "https://yourdomain.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
};
}
}
Nginx 配置
server {
listen 80;
server_name api.example.com;
location /api {
# 添加 CORS 响应头
add_header 'Access-Control-Allow-Origin' 'http://localhost:5173' 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;
# 处理 OPTIONS 预检请求
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
方案三:使用 JSONP(仅支持 GET 请求,不推荐)
JSONP 是一种老旧的跨域解决方案,现在很少使用。
项目配置示例
当前项目配置
vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
plugins: [vue(), vueDevTools()],
server: {
proxy: {
'/api': {
target: env.VITE_API_BASE_URL || 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '/api/')
}
}
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
}
})
src/api/request.ts
import axios, { type AxiosError, type InternalAxiosRequestConfig } from 'axios'
import { ElMessage } from 'element-plus'
const request = axios.create({
// 开发环境使用代理,生产环境使用完整 URL
baseURL: import.meta.env.DEV ? '/api' : import.meta.env.VITE_API_BASE_URL,
timeout: 60000,
headers: {
'Content-Type': 'application/json'
}
})
// Request interceptor
request.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error: AxiosError) => {
return Promise.reject(error)
}
)
// Response interceptor
request.interceptors.response.use(
(response) => {
return response.data
},
(error: AxiosError) => {
const message = (error.response?.data as { message?: string })?.message || '请求失败'
ElMessage.error(message)
return Promise.reject(error)
}
)
export default request
代理配置详解
changeOrigin
proxy: {
'/api': {
target: 'https://juleon.site',
changeOrigin: true // 修改请求头中的 origin
}
}
true: 将请求头的 origin 改为目标 URLfalse: 保持原始 origin
rewrite
proxy: {
'/api': {
target: 'https://juleon.site',
rewrite: (path) => path.replace(/^\/api/, '')
}
}
示例:
- 前端请求:
/api/users - 不使用 rewrite:转发到
https://juleon.site/api/users - 使用 rewrite:转发到
https://juleon.site/users
secure
proxy: {
'/api': {
target: 'https://juleon.site',
secure: false // 忽略 SSL 证书验证(仅开发环境)
}
}
configure
proxy: {
'/api': {
target: 'https://juleon.site',
configure: (proxy, options) => {
// 自定义代理行为
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('发送请求:', req.method, req.url)
})
proxy.on('proxyRes', (proxyRes, req, res) => {
console.log('收到响应:', proxyRes.statusCode)
})
}
}
}
常见问题
Q1: 代理配置后仍然跨域?
A: 检查以下几点:
- 确保重启了开发服务器
- 检查 baseURL 配置是否正确
- 检查浏览器控制台的实际请求 URL
Q2: 生产环境如何处理跨域?
A: 生产环境有两种方案:
- 后端配置 CORS 响应头(推荐)
- 使用 Nginx 反向代理
Q3: 如何调试代理?
A: 在 vite.config.ts 中添加日志:
proxy: {
'/api': {
target: 'https://juleon.site',
changeOrigin: true,
configure: (proxy) => {
proxy.on('proxyReq', (proxyReq, req) => {
console.log('代理请求:', req.method, req.url)
})
proxy.on('error', (err) => {
console.log('代理错误:', err)
})
}
}
}
Q4: 如何处理 WebSocket 跨域?
A: 配置 WebSocket 代理:
proxy: {
'/ws': {
target: 'ws://localhost:3000',
ws: true,
changeOrigin: true
}
}
Q5: 多个后端服务如何配置?
A: 配置多个代理规则:
proxy: {
'/api/user': {
target: 'https://user-service.com',
changeOrigin: true
},
'/api/order': {
target: 'https://order-service.com',
changeOrigin: true
}
}
最佳实践
✅ 推荐做法
- 开发环境使用代理:避免在开发时配置后端 CORS
- 生产环境配置 CORS:在后端或 Nginx 配置 CORS
- 使用环境变量:不同环境使用不同的 API 地址
- 统一请求封装:使用 axios 拦截器统一处理
- 错误处理:统一处理跨域错误
❌ 避免做法
- 不要在生产环境使用
Access-Control-Allow-Origin: * - 不要禁用浏览器的安全策略
- 不要在前端代码中硬编码 API 地址
- 不要忽略 CORS 预检请求(OPTIONS)
完整示例
开发环境配置
// vite.config.ts
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
server: {
proxy: {
'/api': {
target: env.VITE_API_BASE_URL,
changeOrigin: true,
configure: (proxy) => {
proxy.on('error', (err) => {
console.log('代理错误:', err)
})
}
}
}
}
}
})
// src/api/request.ts
const request = axios.create({
baseURL: import.meta.env.DEV ? '/api' : import.meta.env.VITE_API_BASE_URL,
timeout: 60000
})
# .env.development
VITE_API_BASE_URL=https://juleon.site/api
生产环境 Nginx 配置
server {
listen 80;
server_name yourdomain.com;
# 前端静态文件
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
# API 代理
location /api {
add_header 'Access-Control-Allow-Origin' 'https://yourdomain.com' 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 https://juleon.site;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
参考资源
- MDN - CORS
- Vite 服务器选项
- http-proxy-middleware
以上来自Juleon博客,转载请注明出处。
热门推荐
JavaScript通过Node.js进行后端开发指南
2025-06-14 08:54docker-compose常用命令,docker资源占用过高处理
2025-04-28 12:26最新最全最常用的前端Vue 3的20 道面试题分析,案例,教程,学这一篇就够了!
2025-07-04 09:51Express.js 入门之如何学会使用
2025-06-09 07:14博客项目代码自动部署jenkins+gitee
2025-06-25 09:06MongoDB数据库该如何备份,又如何去恢复呢?mongodump与mongorestore学习记录拿走!
2025-05-15 10:18

评论 (0)