跨域问题,作为前端开发者来看就是很平常的问题,通常的解决方案就是在webpack或者vite配置文件中配置一下,跨域问题就能轻松解决,那么你有没有想过为什么会出现跨域问题呢,为什么在webpack或者vite配置文件中配置一下,跨域问题就能轻松解决呢?这背后的原理是什么呢?下面就讲讲我对跨域的理解.
带着以下问题,来学习跨域吧!
- 什么是跨域?
- 什么是同源策略?
- 跨域的有哪些解决方法?
- node在跨域中扮演什么角色?
- webpack配置文件中配置跨域代理背后的原理
1. 跨域
跨域(Cross-Origin)
指的是一个Web应用(运行在一个“源”上)试图去请求另一个“源”的资源。这个行为违反了浏览器的同源策略(Same-Origin Policy)
,因此会被浏览器阻止。
ajax请求时,浏览器要求当前网页和sever必须同源(安全)
这里要强调的一点是:是我能发送请求。但是我的数据回不来(服务端是返回了数据的),但是因为受到【同源策略】限制的原因,浏览器(ajax引擎)将我的响应拦截了,所以我拿不到数据.
2. 同源策略
同源:协议(例如: http, https)、域名 (例如: www.example.com, localhost)、端口 (例如: 80, 443, 8080),三者必须一致
例如:
我的地址是http://127.0.0.1:8080/index.html
要请求的URL:
https://127.0.0.1:8080/index.html 不同源,应为协议
不一样
http://www.example.com:8080/index.html 不同源,应为域名
不一样
http://127.0.0.1:8081/index.html 不同源,应为端口
不一样
http://127.0.0.1:8081/aa/bb 同源,应为协议 域名 端口
都一样
2.1 为什么需要同源策略
当然是为了安全
! 这是一个至关重要的安全机制。
没有同源策略会非常危险。想象一下:
-
你刚在 https://your-bank.com 登录了你的网银。
-
然后你不小心访问了一个恶意网站 http://evil-site.com。
-
如果没有同源策略,evil-site.com 的脚本就可以随意向 your-bank.com 发起请求。因为你的浏览器还带着网银的登录凭证(Cookies),它就能冒充你的身份,获取你的余额、进行转账等操作。
同源策略就像是你家小区的门禁卡系统。A小区的住户(同源)可以自由进出A小区,但不能用A小区的门禁卡进入B小区(不同源),这样就保证了各个小区的安全。
2.2 哪些操作会受到同源策略的限制
主要是那些可能读取或操作其他源数据的交互性行为:
*Ajax / Fetch 请求
:最常见的情况。你不能用 JavaScript 直接通过 XMLHttpRequest
或 Fetch API
从一个不同源的服务器获取数据。
-
Web Socket
和Server-Sent Events
请求。 -
读取不同源的
Cookie、LocalStorage、IndexedDB
。 -
操作不同源的 DOM(例如通过 iframe 嵌入的页面)。
2.3 哪些操作不受同源策略的限制 (可以跨域)
有一些资源的嵌入是允许的,因为这是Web开放性的基础,但这些嵌入的资源不能被当前页面的JavaScript读取内容:
<img src="...">
(图片)<link rel="stylesheet" href="...">
(CSS)<script src="...">
(脚本)<iframe src="...">
(虽然能嵌入,但父页面无法操作其内容)
3. 跨域的有哪些解决方法
既然跨域是浏览器的行为,那么解决方案就是“说服”浏览器允许这次请求
- CORS (跨域资源共享) - 最正统的方案
- 这是后端的解决方案。
- 后端服务器在响应头中设置一系列以
"access-control-allow-origin": "*",
开头的字段(例如Access-Control-Allow-Origin: https://your-site.com
),告诉浏览器:“我允许这个来源的网站来请求我”。 - 浏览器看到这个响应头后,就会放行前端应用接收到的数据
- JSONP
一个古老的技巧,利用 <script>
标签不受同源策略限制的特性来实现跨域。只能发起 GET 请求,且不安全,现在基本已被 CORS 取代。
jsonp 实现:
在前端提前定义好一个函数的名字,后端返回的是一个 函数名() 的代码片段,数据可以通过参数带回来,就相当于调用了前端提前定义的函数
<script>let oscript = document.createElement("script");ocscript.src = "https://localhost:8000/api/aaa?callback=jsonpCallback";document.body.appendChild(ocscript);// 后端会返回一个名字为jsonpCallback({name:'xiaoming'})的代码片段// 相当于调用jsonpCallback(data)的函数function jsonpCallback(data) {console.log(data, "==========data=========="); // {name:'xiaoming'}}
</script>
- 开发环境代理 (Proxy) - 最常用的开发阶段方案
- 这是前端开发环境的解决方案,也就是你问题中提到的 Webpack 或 Vite 的做法。
- 原理:浏览器不是有同源限制吗?那我就不直接请求后端API了。我让我的前端应用去请求我本地的开发服务器(同源),然后让本地开发服务器代为转发这个请求到真正的后端服务器。
- 因为服务器之间的请求(Node.js发起的请求)不受浏览器同源策略的限制,所以可以成功。这样就“欺骗”了浏览器。
4. webpack配置文件中配置跨域代理背后的原理
简单来说,Webpack 配置解决跨域,并不是让浏览器取消了同源策略,而是“欺骗”了浏览器。它通过在本地启动一个代理服务器,将你的请求“转发”到目标服务器,从而避免了浏览器的跨域限制。
4.1 Webpack DevServer 的解决方案:代理 (Proxy)
Webpack 本身只是一个模块打包工具,解决跨域能力来自于它生态中的一个重要组件:webpack-dev-server
。
当你运行 npm run dev
或类似的开发命令时,webpack-dev-server
会启动一个本地开发服务器(通常默认在 http://localhost:8080),并同时提供静态文件服务和代理转发能力。
核心原理:绕开浏览器的同源策略
既然浏览器不允许 localhost:8080 直接请求 api.example.com,那我们就不直接请求。我们让浏览器去请求一个“看起来”是同源的地址,然后让一个中间人(代理服务器)去帮我们请求真正的目标地址。
这个过程如下图所示:
假设你的 webpack.config.js
中有如下配置(或在 vue.config.js / webpack-dev-server 的配置中):
module.exports = {// ... 其他配置 ...devServer: {proxy: {'/api': { // 请求路径前缀:拦截所有以 `/api` 开头的请求target: 'https://api.example.com', // 这是你要代理到的真实后端服务器地址changeOrigin: true, // 关键配置:更改请求头中的OriginpathRewrite: {'^/api': '' // 重写路径:将请求路径中的 `/api` 前缀去掉}}}}
};
一次请求的详细流程:
- 前端代码发起请求:
你的前端代码中,你写的是:axios.get('/api/users')
。
此时,浏览器认为你要请求的是同源(http://localhost:8080)
下的 /api/users,所以它会愉快地发出请求:http://localhost:8080/api/users
。这里没有跨域问题。
- DevServer 代理拦截:
webpack-dev-server
一直在监听来自浏览器的请求。它根据你配置的 proxy 规则,发现这个请求的路径 (/api/users
) 是以 /api 开头的,匹配上了规则。
- 代理服务器转发请求:
DevServer 的代理服务器会替你向真正的目标地址发起一个新的 HTTP 请求。这个请求的地址是:
target + 重写后的路径 = https://api.example.com + /users
。
关键点:这个请求是从你的本地 Node.js 服务(代理服务器)发出的,是服务器对服务器的请求。而服务器之间的通信不受浏览器同源策略的限制,所以不存在跨域问题。
- 接收响应并返回给浏览器:
代理服务器从 https://api.example.com/users 拿到响应数据后,再原封不动地返回给你的浏览器。
- 浏览器收到响应:
浏览器认为这个响应来自于 http://localhost:8080(它的同源服务器),所以它会愉快地接收数据,你的前端代码也就成功拿到了结果。
关键配置项:changeOrigin
的作用
这是一个非常巧妙且重要的配置。
-
是什么:它决定了是否更改原始请求头中的 Host 和 Origin 字段。
-
为什么需要它:有些后端服务器会校验 Origin 或 Host 头来做安全验证或虚拟主机路由。如果后端发现请求来自 localhost:8080,可能会拒绝请求。
-
如何工作:当 changeOrigin: true 时,代理服务器在向后端发送请求时,会将请求头中的 Origin 和 Host 字段修改为 target 的值(即 api.example.com)。这样对于后端服务器来说,这个请求看起来就像是来自同一个域下的普通请求,从而避免了服务端的 CORS 校验。
-
简单来说:它让代理请求“伪装”成同源请求,骗过后端服务器。
5. node在跨域中扮演什么角色?
webpack-dev-server
启动的本地开发服务器(包括其代理功能)就是一个由 Node.js 创建和运行的 HTTP 服务器。
- 本质是一个 Node.js 应用程序
-
webpack-dev-server
本身是一个 npm 包,它被安装在你项目的node_modules
目录中。 -
当你运行
npm run dev
(或类似的命令)时,你实际上是在执行 node_modules/.bin/ 目录下的一个脚本,这个脚本会启动一个 Node.js 进程。 -
这个
Node.js
进程会执行webpack-dev-server
包的代码,从而创建并启动一个 HTTP 服务器。
- 底层依赖的 Node.js 模块
这个由 Node.js 创建的服务器,其底层通常会使用核心模块或流行的框架来处理网络请求,例如:
-
http 模块:Node.js 内置的核心模块,用于创建 HTTP 服务器和客户端。
-
express 框架:一个非常流行的、基于 Node.js 的 Web 应用框架。webpack-dev-server 的内部大量使用了 express 来搭建服务器、定义路由和中间件。
-
http-proxy-middleware:一个专门用于 Express 的代理中间件。这就是实现代理转发功能的核心库。当你配置 devServer.proxy 时,webpack-dev-server 实际上就是在内部自动为你创建并使用了这个中间件。
- 代理过程在 Node.js 环境中完成
让我们重温一下代理过程,这次从 Node.js 的角度看:
-
启动:你输入 npm run dev,一个 Node.js 进程被启动,并在本地(如 localhost:8080)成功运行了一个 Express 服务器。
-
拦截:浏览器请求 http://localhost:8080/api/users。这个请求被发送到你本地运行的 Node.js (Express) 服务器。
-
转发:Express 服务器上的 http-proxy-middleware 中间件根据配置规则,识别出 /api 前缀。然后,它使用 Node.js 的 http 或 https 模块,以程序的方式(而非浏览器方式)向目标服务器 https://api.example.com 发起一个新的 HTTP 请求。这个动作完全发生在你的电脑后台,是服务器对服务器的通信。
-
响应:Node.js 服务器收到 api.example.com 返回的数据。
-
返回:Express 服务器再将这个数据作为响应,发回给浏览器。
这个微型服务器 就是 node!