Code前端首页关于Code前端联系我们

Vue2项目里跨域问题咋解决?

terry 19小时前 阅读数 9 #Vue
文章标签 Vue2 跨域

做Vue2项目的时候,不少同学会碰到接口请求被浏览器拦截、控制台蹦出“跨域”相关错误的情况,这事儿看起来有点头疼,但把原理和解决办法理清楚后,其实没那么复杂,接下来咱们就一步步拆解Vue2里的跨域问题,从为啥出现跨域,到开发、生产环境分别咋处理,再到常见坑点避坑,把这些事儿说明白。

先搞懂“跨域”到底是啥?

首先得明白浏览器的「同源策略」——它是浏览器为了安全搞的一套规则,要求网页里发的请求,必须和当前页面“同源”,那啥算同源?得满足三个条件:协议(比如http/https)、域名、端口都一模一样,要是有一个不一样,浏览器就会把这个请求拦下来,这就是“跨域”错误的根源。

举个例子,你Vue项目开发时,前端页面跑在 http://localhost:8080 ,但要请求的后端接口是 https://api.example.com/user ,这时候协议(http vs https)、域名(localhost vs api.example.com)都不一样,浏览器就会判定这是跨域请求,直接拦截,控制台就会报错。

Vue2开发环境为啥容易碰到跨域?

开发阶段,咱们一般用 webpack-dev-server 启动本地服务,前端页面默认跑在 localhost:8080(或者你改了端口的话另说),但后端接口呢?可能是公司测试环境的域名,也可能是本地另一个服务(比如后端用Node跑在 localhost:3000),这时候前端和后端的“协议、域名、端口”很难完全一致,跨域问题就冒出来了。

不过别慌,开发环境有个很方便的解决思路:用代理绕开浏览器的同源策略,因为浏览器只拦前端发的请求,要是让“中间层”(代理服务器)帮咱们转发请求,浏览器就会认为请求是发给自己人(localhost)的,自然不会拦截~

开发环境用代理解决跨域(vue-cli 2.x 场景)

Vue2项目如果是用 vue-cli 2.x 创建的,配置代理主要改 config/index.js 里的 proxyTable,下面一步步讲咋配。

找到并修改 proxyTable 配置

打开项目里的 config/index.js,找到 dev 环境下的 proxyTable,假设后端接口的基础域名是 http://api.test.com,前端所有以 /api 开头的请求都要转发到这个后端,配置可以这么写:

proxyTable: {
  '/api': {
    target: 'http://api.test.com', // 后端接口的真实域名
    changeOrigin: true, // 关键!让代理服务器假装自己是后端域名,骗过浏览器
    pathRewrite: {
      '^/api': '' // 把请求里的/api前缀删掉,避免后端404
    }
  }
}

这里重点解释两个配置:

  • changeOrigin: true:浏览器发请求时,请求头里的 Originlocalhost:8080,后端如果校验Origin会直接拒绝,开启这个后,代理服务器转发请求时,会把 Origin 改成 http://api.test.com,后端就会认为这是自己人发的请求,不会拦。
  • pathRewrite:假设后端接口本身没有 /api 前缀,前端写请求的时候加了 /api/api/user/list),通过这个配置把 /api 替换成空字符串,实际转发到 http://api.test.com/user/list,避免后端因为路径不对返回404。

前端代码里咋发请求?

axios 举个例子,假设要请求用户列表接口 /user/list,代码可以这么写:

import axios from 'axios';
axios.get('/api/user/list')
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.error('请求出错:', error);
  });

这时候,前端发的请求是 http://localhost:8080/api/user/list,代理服务器会把它转发到 http://api.test.com/user/list,因为浏览器看请求是发给 localhost:8080(同源),所以不会拦截,完美绕开跨域~

生产环境跨域咋处理?

开发环境靠代理能解决,但生产环境部署后,前端代码是打包好的静态文件(比如放在Nginx或CDN上),这时候没有 webpack-dev-server 帮咱们代理了,得换其他方法,常见思路有两种:后端配CORS 或者 用Nginx反向代理

后端设置CORS(跨域资源共享)

CORS是后端在响应头里加一些特殊字段,告诉浏览器“允许这个域名的前端来请求我”,不同后端语言配置方式不一样,举个Node.js + Express的例子:

const express = require('express');
const app = express();
// 全局中间件处理CORS
app.use((req, res, next) => {
  // 允许前端域名访问,生产别用*,换成自己的前端域名!
  res.header('Access-Control-Allow-Origin', 'http://your-frontend-domain.com'); 
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // 允许的请求头
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); // 允许的请求方法
  next();
});
// 后面写接口路由...
app.get('/user/list', (req, res) => {
  res.send({ code: 200, data: [] });
});
app.listen(3000, () => {
  console.log('后端服务启动在3000端口');
});

这样配置后,前端请求这个后端时,浏览器收到响应头里的 Access-Control-Allow-Origin,就知道“哦,这个域名是允许的”,不会拦截请求,但要注意:

  • 生产环境别偷懒用 (允许所有域名),得指定自己的前端域名,否则容易被恶意网站利用,有安全风险。
  • 如果前端要发 复杂请求(比如带自定义头、用PUT/DELETE方法),浏览器会先发一个 OPTIONS 预检请求,这时候后端得单独处理 OPTIONS 请求,返回允许的头和方法,否则还是会跨域,比如Express里可以加这段:
app.options('*', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'http://your-frontend-domain.com');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.sendStatus(200);
});

Nginx反向代理(让前后端“同域”)

如果前端和后端部署在同一台服务器(或通过Nginx管理域名),可以用Nginx把前端和后端的请求“捏”到同一个域名下,原理是:前端请求 yourdomain.com/api,Nginx把这个请求转发到后端服务,这样浏览器看请求是发给 yourdomain.com(同源),自然不会跨域。

举个Nginx配置的例子:

server {
  listen 80;
  server_name yourdomain.com; # 你的域名
  # 处理前端静态文件请求
  location / {
    root /usr/local/nginx/html; # 前端dist包的存放路径
    index index.html index.htm;
    try_files $uri $uri/ /index.html; # 单页应用路由处理
  }
  # 处理/api开头的后端请求
  location /api {
    proxy_pass http://backend-server:3000; # 后端服务的地址(比如localhost:3000)
    proxy_set_header Host $host; # 把请求头里的Host改成当前域名
    proxy_set_header X-Real-IP $remote_addr; # 传递真实客户端IP
  }
}

这样配置后,前端代码里请求 /api/user/list,Nginx会转发到 http://backend-server:3000/user/list(因为 pathRewrite 类似的逻辑Nginx也能处理,这里省略了,实际可能需要调整路径),好处是前端不用改代码,后端也不用配CORS,因为请求是Nginx转发的,属于“服务端之间的请求”,没有浏览器的同源策略限制~

跨域解决里的常见误区和坑

不少同学配置完后还是报错,大概率是掉坑里了,下面列几个常见问题,帮你避坑:

开发环境proxy配置后没生效?

检查这几点:

  • 配置改的是 config/index.js 里的 dev 环境吗?因为 build 环境(生产打包)不用proxy,改错环境等于白改。
  • 改完配置有没有重启项目?vue-cli 的dev server需要重启才会加载新配置,光保存没用。
  • 前端请求的URL是不是以代理前缀开头?比如配置了 /api,但请求写的是 http://api.test.com/user/list,这时候不会走代理,浏览器直接发请求,自然跨域。

生产环境CORS配置了还跨域?

这种情况得仔细查响应头:

  • 后端是不是真的返回了 Access-Control-Allow-Origin?有时候后端框架配置不对,或者中间有网关、反向代理把响应头“吃掉”了,导致浏览器没收到这个头。
  • 是不是发了复杂请求(比如带 Authorization 头、用 PUT 方法)?这时候得确保后端处理了 OPTIONS 预检请求,否则浏览器会直接拦截。

用了proxy还报跨域?

这种情况大概率是代理配置错了,

  • target 写错了域名或端口,导致代理转发到错误地址,后端返回404,浏览器误以为是跨域错误(其实是请求本身失败,但跨域错误提示更显眼)。
  • changeOrigin: false(默认是false),导致代理服务器发请求时,Origin还是 localhost:8080,后端因为Origin不匹配拒绝请求,浏览器就报跨域。
  • pathRewrite 规则不对,比如前端请求 /api/user,代理后变成 http://api.test.com/api/user(因为没把 /api 替换掉),后端没有这个路径,返回404,也会被误认为跨域。

除了代理和CORS,还有没有其他办法?

有的,但场景比较有限,简单提两种:

JSONP(只支持GET请求)

JSONP利用 script 标签没有跨域限制的特点,前端动态创建 script 标签,后端返回一个回调函数包裹的JSON数据,但现在用得少,因为:

  • 只支持 GET 请求,POST等方法用不了。
  • axios 默认不支持JSONP,得自己写逻辑或者用插件,麻烦。

WebSocket(适合实时通信)

WebSocket协议(ws://wss://)本身在建立连接时,浏览器也会检查 Origin 头,所以后端得允许对应的前端域名,但WebSocket属于实时通信场景(比如聊天、股票行情),不是HTTP请求的跨域解决,所以一般不用来处理普通接口请求。

不同场景选不同方案

最后帮你理清楚什么时候用啥方案:

  • 开发环境:优先用 vue-cliproxyTable,配置简单,不用麻烦后端同学,改完重启项目就能用。
  • 生产环境
    • 如果后端愿意配合,用CORS最灵活,前端不用改代码,后端加几个响应头就行。
    • 如果部署时能控制Nginx(或其他反向代理工具),用反向代理把前后端搞到同域名下,彻底绕开跨域问题,还能顺道做性能优化(比如缓存、负载均衡)。

跨域问题的本质是浏览器的安全限制,服务端之间互相调接口(比如后端调另一个后端)不存在跨域问题,所以代理和CORS的核心逻辑,都是让浏览器“认为”这个请求是合法的,或者让服务端主动允许跨域请求~

现在再回头看跨域问题,是不是觉得清晰多了?下次碰到跨域错误,先分清是开发还是生产环境,再对应选方案,排查配置细节,基本都能解决~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门