前端跨域請求原理及實踐

在前端開發中,"跨域"是一個繞不開的話題。當我們的頁面嘗試從一個域名請求另一個域名的資源時,瀏覽器往往會拋出類似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
英文地址,請注明出處:http://en.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;僅保留符合特定…