前端跨域请求原理及实践

在前端开发中,"跨域"是一个绕不开的话题。当我们的页面尝试从一个域名请求另一个域名的资源时,浏览器往往会抛出类似Access to fetch at 'xxx' from origin 'xxx' has been blocked by CORS policy的错误。下面将深入探讨跨域请求的底层原理,并介绍多种解决跨域问题和解决方案。

一、跨域的本质:同源策略

要理解跨域,首先需要了解浏览器的同源策略(Same-Origin Policy)。这是浏览器最核心的安全功能之一,由Netscape在1995年引入,其目的是防止恶意网页窃取另一个网页的敏感数据。

1.1 什么是"同源"?

两个URL被视为"同源",必须同时满足以下三个条件:

  • 协议相同(如都是http或https)
  • 域名相同(如都是example.com,而非a.example.com和b.example.com)
  • 端口相同(如都是80端口,默认端口可省略)

举例说明:

当前页面URL请求目标URL是否同源原因
http://example.comhttp://example.com/page三要素完全相同
http://example.comhttps://example.com协议不同(http vs https)
http://example.comhttp://api.example.com域名不同(主域 vs 子域)
http://example.com:80http://example.com:8080端口不同(80 vs 8080)

1.2 同源策略的限制范围

同源策略主要限制以下几种交互:

  • DOM访问:禁止不同源页面之间的DOM操作(如iframe嵌套的跨域页面)
  • 数据读取:禁止读取不同源的Cookie、LocalStorage、SessionStorage
  • 网络请求:禁止通过XMLHttpRequest、Fetch API等方式发起跨域HTTP请求

注意:并非所有跨域请求都会被禁止。像<img><script><link>等标签的资源加载不受同源策略限制,这也是后续某些跨域解决方案的技术基础。

二、跨域请求的类型:简单请求与预检请求

当浏览器检测到跨域请求时,会根据请求的特征将其分为两类,并采取不同的处理策略:

2.1 简单请求(Simple Request)

同时满足以下条件的请求被视为简单请求:

  1. 请求方法为以下三种之一:GETHEADPOST
  2. 请求头仅包含浏览器默认字段或以下字段:AcceptAccept-LanguageContent-LanguageContent-Type(仅限特定值)
  3. Content-Type的值只能是:application/x-www-form-urlencodedmultipart/form-datatext/plain

简单请求的处理流程

  1. 浏览器直接发送请求,并在请求头中添加Origin字段(值为当前页面域名)
  2. 服务器响应时,若包含Access-Control-Allow-Origin且值包含请求的Origin,则浏览器允许前端读取响应;否则拦截响应,抛出跨域错误

2.2 预检请求(Preflight Request)

不满足简单请求条件的跨域请求会触发预检请求,例如:

  • 使用PUTDELETE等特殊请求方法
  • 请求头包含自定义字段(如AuthorizationX-Requested-With
  • Content-Typeapplication/json

预检请求的处理流程

  1. 浏览器先发送一个OPTIONS方法的预检请求,询问服务器是否允许实际请求
  2. 服务器响应预检请求时,通过Access-Control-*系列头字段声明允许的跨域规则
  3. 若服务器允许,浏览器才发送实际请求;否则直接拦截,不发送实际请求

三、跨域解决方案及实践

了解跨域的原理后,我们来介绍几种常用的跨域解决方案,每种方案都将提供完整的代码示例。

3.1 CORS(Cross-Origin Resource Sharing)

CORS是W3C标准推荐的跨域解决方案,通过服务器端设置响应头实现跨域允许,支持所有HTTP方法,是目前最主流的跨域方案。

3.1.1 基本原理

CORS的核心是服务器端通过设置Access-Control-*系列响应头,告知浏览器允许哪些跨域请求。常用头字段包括:

  • Access-Control-Allow-Origin:允许的源(如https://example.com*表示允许所有)
  • Access-Control-Allow-Methods:允许的请求方法(如GET, POST, PUT
  • Access-Control-Allow-Headers:允许的请求头
  • Access-Control-Allow-Credentials:是否允许携带凭证(Cookie等)
  • Access-Control-Max-Age:预检请求的缓存时间(避免重复发送预检请求)
3.1.2 代码示例

前端代码(使用Fetch API)

// 前端页面地址:http://localhost:3000
fetch('http://localhost:4000/api/data', {method: 'POST',headers: {'Content-Type': 'application/json','X-Custom-Header': 'custom-value' // 自定义头,会触发预检请求},body: JSON.stringify({ name: '前端请求' }),credentials: 'include' // 允许携带Cookie
})
.then(response => response.json())
.then(data => console.log('跨域请求成功:', data))
.catch(error => console.error('跨域请求失败:', error));

后端代码(Node.js + Express)

// 服务器地址:http://localhost:4000
const express = require('express');
const app = express();
app.use(express.json());// CORS配置中间件
app.use((req, res, next) => {// 允许的源(生产环境建议指定具体域名,而非*)res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');// 允许的请求方法res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');// 允许的请求头(需包含前端实际使用的所有自定义头)res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header');// 允许携带凭证(Cookie等)res.setHeader('Access-Control-Allow-Credentials', 'true');// 预检请求缓存时间(秒)res.setHeader('Access-Control-Max-Age', '86400'); // 24小时// 处理预检请求(直接返回204)if (req.method === 'OPTIONS') {return res.sendStatus(204);}next();
});// 接口路由
app.post('/api/data', (req, res) => {console.log('收到跨域请求数据:', req.body);res.json({ status: 'success', message: '跨域请求处理完成',data: req.body});
});app.listen(4000, () => {console.log('CORS服务器运行在 http://localhost:4000');
});

注意:当Access-Control-Allow-Credentials设为true时,Access-Control-Allow-Origin不能设为*,必须指定具体域名。

3.2 JSONP(JSON with Padding)

JSONP是一种古老但兼容性极佳的跨域方案(支持IE等老浏览器),其原理是利用<script>标签不受同源策略限制的特性,通过动态创建<script>标签发起跨域请求。

3.2.1 基本原理
  1. 前端定义一个回调函数(如handleJsonpResponse
  2. 前端动态创建<script>标签,其src指向跨域接口,并在URL中携带回调函数名(如?callback=handleJsonpResponse
  3. 服务器接收到请求后,将数据包裹在回调函数中返回(如handleJsonpResponse({...})
  4. 浏览器加载<script>后,自动执行回调函数,前端即可获取数据
3.2.2 代码示例

前端代码

<!-- 前端页面地址:http://localhost:3000 -->
<script>
// 定义回调函数
function handleJsonpResponse(data) {console.log('JSONP跨域请求成功:', data);
}// 动态创建script标签发起请求
function requestJsonp() {const script = document.createElement('script');// 跨域接口地址,携带回调函数名script.src = 'http://localhost:4000/api/jsonp?callback=handleJsonpResponse&name=jsonp请求';document.body.appendChild(script);// 请求完成后移除script标签script.onload = () => script.remove();script.onerror = () => {console.error('JSONP请求失败');script.remove();};
}
</script><button onclick="requestJsonp()">发起JSONP请求</button>

后端代码(Node.js + Express)

// 服务器地址:http://localhost:4000
const express = require('express');
const app = express();app.get('/api/jsonp', (req, res) => {const { callback, name } = req.query;console.log('收到JSONP请求参数:', name);// 构造响应数据(用回调函数包裹)const data = {status: 'success',message: 'JSONP请求处理完成',data: { name }};// 返回JavaScript代码(执行回调函数)res.send(`${callback}(${JSON.stringify(data)})`);
});app.listen(4000, () => {console.log('JSONP服务器运行在 http://localhost:4000');
});

局限性

  • 仅支持GET请求
  • 安全性风险(可能遭受XSS攻击)
  • 无法捕获HTTP错误状态码(如404、500)

3.3 代理服务器

代理服务器是开发环境中常用的跨域解决方案,其原理是:由于浏览器的同源策略只限制前端脚本,不限制服务器之间的通信,因此可以通过一个与前端同源的代理服务器转发请求到目标服务器。

3.3.1 开发环境代理(以Vite为例)

在前端项目中(如Vue、React),可通过开发服务器配置代理,解决开发阶段的跨域问题。

Vite配置示例(vite.config.js)

// 前端开发服务器:http://localhost:5173
export default {server: {// 配置代理proxy: {// 匹配所有以/api开头的请求'/api': {target: 'http://localhost:4000', // 目标服务器地址changeOrigin: true, // 发送请求时,将Host头改为目标服务器地址// 可选:重写路径(如果目标接口没有/api前缀)// rewrite: (path) => path.replace(/^\/api/, '')}}}
};

前端请求代码

// 此时请求的是同源的开发服务器(http://localhost:5173),无跨域问题
// 开发服务器会自动转发到 http://localhost:4000/api/data
fetch('/api/data', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ name: '通过代理请求' })
})
.then(response => response.json())
.then(data => console.log('代理请求成功:', data));
3.3.2 生产环境代理(Nginx)

生产环境中,可通过Nginx反向代理实现跨域,配置示例如下:

# Nginx配置
server {listen 80;server_name localhost;# 前端页面所在目录location / {root /path/to/frontend;index index.html;}# 代理跨域请求location /api/ {# 目标服务器地址proxy_pass http://localhost:4000/api/;# 传递原始请求头proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;# 可选:设置CORS头(如果目标服务器未设置)add_header Access-Control-Allow-Origin *;}
}

配置后,前端直接请求/api/data,Nginx会自动转发到http://localhost:4000/api/data,避免跨域问题。

3.4 其他跨域方案

3.4.1 iframe + postMessage

适用于两个跨域页面之间的通信(如父页面与iframe嵌套页面):

父页面(http://parent.com)

<iframe id="childFrame" src="http://child.com"></iframe><script>
// 向子页面发送消息
const frame = document.getElementById('childFrame');
frame.onload = () => {frame.contentWindow.postMessage({ type: 'greeting', data: 'Hello from parent' },'http://child.com' // 限制接收域);
};// 接收子页面消息
window.addEventListener('message', (event) => {// 验证消息来源if (event.origin !== 'http://child.com') return;console.log('收到子页面消息:', event.data);
});
</script>

子页面(http://child.com)

// 接收父页面消息
window.addEventListener('message', (event) => {if (event.origin !== 'http://parent.com') return;console.log('收到父页面消息:', event.data);// 向父页面回复消息event.source.postMessage({ type: 'response', data: 'Hello from child' },event.origin);
});
3.4.2 WebSocket

WebSocket协议本身不受同源策略限制,可直接建立跨域连接:

前端代码

// 建立WebSocket连接(ws/wss协议)
const socket = new WebSocket('ws://localhost:4000');// 连接成功
socket.onopen = () => {console.log('WebSocket连接已建立');socket.send('Hello WebSocket');
};// 接收消息
socket.onmessage = (event) => {console.log('收到WebSocket消息:', event.data);
};

后端代码(Node.js + ws库)

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 4000 });wss.on('connection', (ws) => {console.log('客户端已连接');ws.on('message', (message) => {console.log('收到消息:', message.toString());ws.send('服务器收到:' + message.toString());});
});

四、总结与最佳实践

跨域请求的解决方案各有优缺点,选择时需根据实际场景判断:

方案优点缺点适用场景
CORS功能完善、支持所有HTTP方法、安全性高需要服务器配合、老浏览器兼容问题现代Web应用(推荐)
JSONP兼容性好(支持IE)仅支持GET、安全性差需兼容老浏览器的场景
代理服务器前端无需修改、开发/生产均可用需要额外配置服务器开发环境调试、生产环境跨域
iframe + postMessage适合页面间通信仅用于页面交互、不适合API请求父页面与iframe跨域通信
WebSocket全双工通信、无跨域限制需专门协议、不适合普通API请求实时通信场景(如聊天、通知)

最佳实践建议

  1. 优先使用CORS,这是最标准、最安全的跨域方案
  2. 开发环境使用代理服务器(如Vite、Webpack代理)提高开发效率
  3. 生产环境避免使用*作为Access-Control-Allow-Origin,严格限制允许的源
  4. 涉及用户凭证的请求,确保正确配置Access-Control-Allow-Credentials
  5. 避免使用JSONP,除非有强烈的老浏览器兼容需求

通过本文的介绍,相信你已经对跨域请求的原理和解决方案有了全面的理解。在实际开发中,结合具体场景选择合适的方案,就能轻松解决跨域问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92507.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92507.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot07-数据层的解决方案:SQL

一、内置数据源 1-1、【回顾】Druid数据源的配置 druid的两种导入格式 1-2、springboot提供的3种内置数据源的配置 若是不配置Druid&#xff0c; springboot提供了3中默认的数据源配置&#xff0c;它们分别是&#xff1a; 1. HikariCP&#xff08;默认&#xff09; 从 Spring…

前端自动化埋点:页面模块级行为跟踪与问题定位系统​​的技术设计方案

一、核心设计目标​​精细化监控​​&#xff1a;定位到页面中​​单个模块​​的曝光、点击等行为。​​低侵入性​​&#xff1a;业务代码与埋点逻辑解耦&#xff0c;降低开发维护成本。​​链路可追踪​​&#xff1a;串联用户从曝光到操作的完整行为路径。​​实时性​​&a…

Node.js 与 Java 性能对比

一、核心架构与任务模型对比Node.js 单线程事件循环 非阻塞I/O 通过V8引擎执行JavaScript&#xff0c;采用事件驱动模型&#xff0c;所有I/O操作&#xff08;如网络请求、文件读写&#xff09;均为非阻塞。单线程处理所有请求&#xff0c;但通过事件循环&#xff08;Event Loo…

Python3常见接口函数

Python3常见接口函数一、基础内置函数 输入输出 print()&#xff1a;输出内容input()&#xff1a;读取用户输入 类型转换 int()、float()、str()、bool()&#xff1a;基础类型转换list()、tuple()、set()、dict()&#xff1a;容器类型转换bin()、hex()、oct()&#xff1a;进制转…

《P4092 [HEOI2016/TJOI2016] 树》

题目描述在 2016 年&#xff0c;佳媛姐姐刚刚学习了树&#xff0c;非常开心。现在他想解决这样一个问题&#xff1a;给定一颗有根树&#xff0c;根为 1 &#xff0c;有以下两种操作&#xff1a;标记操作&#xff1a;对某个结点打上标记。&#xff08;在最开始&#xff0c;只有结…

TCP头部

TCP头部字段详解1. 源端口和目的端口&#xff08;各16位&#xff09;功能&#xff1a;标识发送和接收应用程序范围&#xff1a;0-65535&#xff08;0-1023为知名端口&#xff09;技术细节&#xff1a;客户端通常使用临时端口&#xff08;1024-65535&#xff09;服务端使用固定端…

LinkedList与链表(单向)(Java实现)

引入链表结构&#xff1a;在ArrayList任意位置插入或者删除元素时&#xff0c;就需要将后序元素整体往前或者往后 搬移&#xff0c;时间复杂度为O(n)&#xff0c;效率比较低&#xff0c;因此ArrayList不适合做任意位置插入和删除比较多的场景。因此&#xff1a;java集合中又引入…

网络--VLAN技术

目录 VLAN实验报告 一、实验拓扑 二、实验要求 三、实验思路 1、实验准备 2. VLAN 3. DHCP 自动分配 4、 全网可达验证 四、实验步骤 &#xff08;一&#xff09;交换机配置- VLAN 创建与接口划分 &#xff08;二&#xff09;路由器配置&#xff08;R1&#xff0c…

网络基础17--设备虚拟化

一、传统MSTPVRRP的不足传统MSTPVRRP设计&#xff1a;规划复杂&#xff1a;需要详细规划VRRP多实例的Master归属、MSTP的VLAN和生成树实例归属&#xff0c;以及IP网段。收敛速度慢&#xff1a;故障恢复速度一般在秒级&#xff0c;VRRP收敛时间至少需要3秒&#xff0c;故障恢复速…

深入解析Hadoop资源隔离机制:Cgroups、容器限制与OOM Killer防御策略

Hadoop资源隔离机制概述在分布式计算环境中&#xff0c;资源隔离是保障多任务并行执行稳定性的关键技术。Hadoop作为主流的大数据处理框架&#xff0c;其资源管理能力直接影响集群的吞吐量和任务成功率。随着YARN架构的引入&#xff0c;Hadoop实现了计算资源与存储资源的解耦&a…

static 关键字的 特殊性

static 关键字的 “特殊性” 主要体现在其与类、对象的绑定关系&#xff0c;以及由此带来的一些反常规规则&#xff0c;具体如下&#xff1a;生命周期与内存位置特殊静态成员&#xff08;变量 / 方法&#xff09;随类加载而创建&#xff0c;随类卸载而销毁&#xff0c;生命周期…

win10系统Apache以 FastCGI方式运行PHP

文件下载及官方网站 VC运行库Latest下载页:Latest supported Visual C Redistributable downloads | Microsoft Learnapache httpd官网:Welcome! - The Apache HTTP Server Project下载页:Apache VS17 binaries and modules downloadphp官网:PHP: Hypertext Preprocessor下载页…

MCP与企业数据集成:ERP、CRM、数据仓库的统一接入

MCP与企业数据集成&#xff1a;ERP、CRM、数据仓库的统一接入 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特性都是我…

【milvus检索】milvus检索召回率

Milvus中两种核心查询方式&#xff1a;暴力搜索&#xff08;Brute-force Search&#xff09; 和 近似最近邻搜索&#xff08;Approximate Nearest Neighbor, ANN&#xff09;。 逐一计算相似度&#xff1a;这是暴力搜索&#xff0c;能保证100%找到最相似的向量&#xff0c;但速…

docker Neo4j

Day 1 &#xff1a;Docker Desktop 基础熟悉 运行官方 hello-world 测试&#xff1a; docker -run hello-world 运行 Nginx 体验容器暴露端口&#xff1a; docker run -d -p 8080:80 nginx -d --detach 以 分离模式 运行容器 -p --publish 设置 宿主机与容器的端口映射。…

Win10_Qt6_C++_YOLO推理 -(1)MingW-opencv编译

先上效果图&#xff1a; 因为是一个为了尝试跑通的demo&#xff0c;美观、功能都先忽略哈。 一、环境 库版本下载链接备注cmakecmake-4.1.0-rc2-windows-x86_64.msihttps://cmake.org/download/make x86_64-15.1.0-release-posix-seh-ucrt-rt_v12-rev0.7zhttps://github.com/…

day060-zabbix监控各种客户端

文章目录0. 老男孩思想-一个人的背书1. zabbix各种客户端1.1 Windows Server监控1.2 网络设备监控1.3 java应用监控1.4 前端监控java程序故障2. 相关项监控3. 思维导图0. 老男孩思想-一个人的背书 学历、能力、态度、特长、人品、口碑&#xff08;身边的人、领导&#xff09; …

OpenCV 官翻 2 - 图像处理

文章目录色彩空间转换目标色彩空间转换目标追踪如何确定要追踪的HSV值&#xff1f;练习图像的几何变换目标变换缩放翻译旋转仿射变换透视变换其他资源图像阈值处理目标简单阈值化自适应阈值化大津二值化法Otsu二值化算法原理其他资源练习图像平滑处理目标二维卷积&#xff08;图…

动态路由协议基础

一、动态路由协议简介2.动态路由协议的基本功能二、动态路由协议分类对比项距离矢量&#xff08;如 RIP&#xff09;链路状态&#xff08;如 OSPF&#xff09;信息来源只听直接邻居说收集全网链路状态&#xff0c;自己建 “地图”计算逻辑邻居给的距离 1&#xff0c;简单累加用…

netstat -tunlp | grep的作用

​​一、命令整体结构解析​​命令由两部分通过管道符 |连接&#xff1a;netstat -tunlp&#xff1a;核心网络状态统计命令&#xff0c;输出指定类型的网络连接信息&#xff1b;grep&#xff1a;文本搜索工具&#xff0c;用于过滤 netstat的输出结果&#xff0c;仅保留符合特定…