vue前端解决跨域的三种方法 - 完整解决方案与实战教程
在前后端分离的开发模式下,Vue 项目本地跑在 http://localhost:8080,而后端 API 部署在 http://api.example.com 或另一个端口上,浏览器控制台立刻抛出红色报错:Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy。这是每一位前端工程师都会踩的坑,尤其在联调阶段,后端接口还没上线、测试环境域名不固定、或者需要携带 Cookie 做鉴权时,跨域问题会直接卡住整个开发进度,让人误以为是接口写错了或者 axios 配置有问题。本文从实际排坑角度出发,给出三种在 Vue 前端侧解决跨域的实战方案,并附上可直接复制的完整代码。
问题现象
打开 Chrome DevTools 的 Network 面板,请求状态显示为 (failed) net::ERR_FAILED 或 CORS error,Console 中出现类似下面的报错:
Access to XMLHttpRequest at 'http://localhost:3000/api/user'
from origin 'http://localhost:8080' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
典型特征包括:
- 请求能发出,但响应被浏览器拦截,Network 中看到的是红色失败状态。
- 简单请求(GET/POST + 简单头)可能只报缺少
Access-Control-Allow-Origin。 - 带自定义头(如
Authorization)或Content-Type: application/json时,会先发OPTIONS预检请求,预检失败则真实请求根本不会发出。 - Postman 或 curl 请求同一接口一切正常,唯独浏览器里报错。
原因分析
跨域的本质是浏览器的同源策略(Same-Origin Policy):协议、域名、端口三者必须完全一致。Vue 开发服务器默认运行在 localhost:8080(Vite 为 5173),而后端服务通常在不同端口或域名,因此触发跨域限制。
需要明确几个关键点:
- 跨域是浏览器行为,不是服务器行为。请求实际上已经到达后端并返回了数据,只是浏览器拒绝把响应交给 JS。
- 前端无法直接“关闭”同源策略,只能通过代理、CORS 响应头或 JSONP 等手段绕过。
- 开发环境与生产环境的跨域解决方案往往不同:开发用代理,生产用 Nginx 反向代理或后端配置 CORS。
解决方案(附完整代码)
方法一:Vue CLI / Vite 开发服务器代理(推荐开发环境使用)
原理是让开发服务器充当中间人:浏览器请求同源的 /api,开发服务器再转发到真实后端,从而绕过浏览器同源策略。
Vue CLI(webpack)配置:在项目根目录创建或修改 vue.config.js:
// vue.config.js
module.exports = {
devServer: {
port: 8080,
proxy: {
// 匹配所有以 /api 开头的请求
'/api': {
target: 'http://localhost:3000', // 后端真实地址
changeOrigin: true, // 修改请求头中的 Host,伪装成同源
ws: false, // 是否代理 websocket
pathRewrite: {
'^/api': '' // 去掉 /api 前缀再转发给后端
}
}
}
}
}
Vite 配置:修改 vite.config.js:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
前端请求时统一使用相对路径:
// axios 实例配置
import axios from 'axios'
const request = axios.create({
baseURL: '/api', // 走开发服务器代理
timeout: 10000
})
export default request
排查步骤:
- 确认
target地址可被浏览器直接访问(用 Postman 验证)。 - 修改配置文件后必须重启 dev server,热更新不会生效。
- 如果后端接口本身带
/api前缀,则不要配置pathRewrite。 - 代理只在开发环境生效,打包后的生产环境需要 Nginx 或后端 CORS 支持。
方法二:后端配置 CORS 响应头(推荐生产环境使用)
由后端在响应中加上 CORS 相关头,浏览器看到合法头后放行。前端无需任何改动。
以 Node.js + Express 为例:
// server.js
const express = require('express')
const cors = require('cors')
const app = express()
// 允许所有来源(仅测试环境使用)
app.use(cors())
// 生产环境精细化配置
app.use(cors({
origin: ['http://localhost:8080', 'https://www.example.com'], // 白名单
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // 允许携带 Cookie
maxAge: 86400 // 预检结果缓存 24 小时
}))
app.listen(3000, () => console.log('Server running on 3000'))
如果使用 Nginx 反向代理,可在配置中加入:
location /api/ {
proxy_pass http://backend_server/;
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods 'GET, POST, 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;
}
}
前端侧需要配合的配置(携带 Cookie 时):
// axios 全局配置
axios.defaults.withCredentials = true
// 或单个请求
axios.get('/api/user', { withCredentials: true })
注意事项:
- 当
withCredentials: true时,后端Access-Control-Allow-Origin不能为*,必须指定具体域名。 - 预检请求
OPTIONS必须返回 2xx 状态码,否则真实请求不会发出。 - 自定义头(如
token)必须出现在Access-Control-Allow-Headers中。
方法三:JSONP(仅限 GET,兼容老项目)
JSONP 利用 <script> 标签不受同源策略限制的特性,通过回调函数接收数据。仅适用于 GET 请求,且需要后端配合返回 JS 脚本。
// 封装一个简单的 JSONP 函数
function jsonp(url, params = {}, callbackName = 'callback') {
return new Promise((resolve, reject) => {
// 生成唯一回调名,避免多次请求冲突
const cbName = `jsonp_${Date.now()}_${Math.floor(Math.random() * 1000)}`
// 挂载全局回调
window[cbName] = (data) => {
resolve(data)
// 清理:删除 script 标签和全局函数
document.body.removeChild(script)
delete window[cbName]
}
// 拼接参数
const query = new URLSearchParams({ ...params, [callbackName]: cbName }).toString()
const script = document.createElement('script')
script.src = `${url}?${query}`
script.onerror = () => reject(new Error('JSONP request failed'))
document.body.appendChild(script)
})
}
// 使用示例
jsonp('http://localhost:3000/api/user', { id: 1 })
.then(res => console.log(res))
.catch(err => console.error(err))
后端需返回类似:
// 后端根据 callback 参数动态包裹
callback({ "code": 0, "data": { "name": "Tom" } })
JSONP 的局限非常明显:
- 只支持 GET,无法上传文件或提交复杂表单。
- 错误处理能力弱,无法获取 HTTP 状态码。
- 存在 XSS 风险,需严格校验回调名。
- 现代项目建议优先使用代理或 CORS,JSONP 仅作为兼容老系统的兜底方案。
方案选型建议
- 开发环境:优先使用 Vite / Vue CLI 代理,零后端改动,配置简单。
- 生产环境:优先由后端配置 CORS 或 Nginx 反向代理,前端保持相对路径请求。
- 老项目兼容:若后端无法改造,且只涉及 GET,可临时使用 JSONP。
- 携带 Cookie:必须使用 CORS +
withCredentials,代理方案在开发环境同样可行。
排坑的核心思路是:先看 Network 面板确认是预检失败还是响应头缺失,再判断是开发环境还是生产环境,最后选择对应方案。切忌盲目在 axios 里加各种 header,那样只会让问题更复杂。