当前位置: 首页 > news >正文

前端跨域请求原理及实践

在前端开发中,"跨域"是一个绕不开的话题。当我们的页面尝试从一个域名请求另一个域名的资源时,浏览器往往会抛出类似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.dtcms.com/a/295080.html

相关文章:

  • 一二章笔记总结
  • CSP-J系列【2024】P11229 [CSP-J 2024] 小木棍题解
  • 1688官方跨境寻源通API接口调用实战
  • LLM指纹底层技术——混合专家模型
  • CSP-J系列【2023】P9750 [CSP-J 2023] 一元二次方程题解
  • SSH 一键互信配置脚本 V2.0 使用指南
  • 卡尔曼滤波数据融合
  • AI 及开发领域动态与资源汇总(2025年7月23日)
  • 【LeetCode】算法详解#9 ---旋转图像
  • QT开发---基础介绍及环境搭建
  • STM32中SystemCoreClockUpdate函数解读
  • 双写缓冲区 Redo Log
  • 基于GitHub的Terraform自动化管理最佳实践
  • 多服务器批量发布软件
  • Linux编程:9、线程编程-互斥锁与条件变量
  • 扫地机产品的电池CQC认证遵循哪个标准?
  • 1. 一份“从 0 到 1” 的 WSL(Windows Subsystem for Linux)速查手册
  • J2EE模式---视图助手模式
  • ospf多区域
  • git的使用,推送仓库github
  • Hierarchical-Localization 安装与常见问题解决手册
  • MSTP多生成树协议
  • 【西北工业大学公开课】导引系统原理(全61讲)周军 -个人笔记版 5000字
  • 基于多种机器学习的水质污染及安全预测分析系统的设计与实现【随机森林、XGBoost、LightGBM、SMOTE、贝叶斯优化】
  • Parasoft为金融服务打造统一测试平台,提升安全、合规与交付效率
  • Shell函数
  • 无人设备遥控器之无线网络技术篇
  • Linux 一文详谈Vim编辑器的使用
  • 面试150 最大子数组和
  • C语言学习(days09)