引言
在現代網絡應用中,跨域問題是一個常見的挑戰。由于瀏覽器的同源策略,限制了從不同源(域名、協議或端口)進行資源共享,這就造成了跨域訪問的限制。跨域資源共享(CORS)是一種技術,它允許網頁上的腳本能夠與不同源的服務器交互,是解決這一問題的關鍵技術。
跨域資源共享(CORS)簡述
CORS 是一種網絡安全協議,它定義了在服務器和客戶端之間如何安全地實現跨源訪問。通過在服務器端設置特定的HTTP頭部,CORS 允許開發者指定哪些外部資源是可以被網頁訪問的,大幅度提升了網絡應用的互操作性和安全性。
CORS 概念解析
什么是同源策略?
同源策略是瀏覽器的一項安全功能,它限制了一個源中的文檔或腳本如何與另一個源的資源進行交互。這是一個用于隔離潛在惡意文件的重要安全措施,不允許不同源之間的讀取數據。
CORS 是如何工作的?
CORS 通過在服務器端返回一系列 CORS 響應頭來工作,如 Access-Control-Allow-Origin,這些頭信息指示瀏覽器允許哪些網站通過腳本訪問該服務器的資源。如果 CORS 頭信息缺失或值不正確,瀏覽器將阻止任何跨源請求。
實現 CORS 的常用方法
使用 JSONP 解決跨域問題
JSONP(JSON with Padding)是一種早期用于繞過同源策略的技術,它通過動態添加?<script>
?標簽來請求一個帶有回調函數的URL,從而實現跨域請求。這種方法只支持GET請求。
實現 CORS:基本概念和步驟
CORS 的實現涉及在服務器端設置適當的HTTP頭。例如,Access-Control-Allow-Origin 可以被設置為特定的域名或星號(*),表示允許所有域名的跨域請求。
通過 Express.js 設置 CORS
基本 CORS 設置
在 Express.js 中,可以使用?cors
?中間件來簡化 CORS 的配置。例如,可以全局配置應用以接受來自某個特定源的請求:
const cors = require('cors');
app.use(cors({origin: 'http://example.com'
}));
高級 CORS 設置(攜帶憑證、允許多種 HTTP 方法等)
對于需要更復雜的 CORS 配置,如支持憑證或多種HTTP方法,可以詳細配置?cors
?中間件的選項:
app.use(cors({origin: 'http://example.com',methods: ['GET', 'POST'],credentials: true
}));
Node.js 項目實戰演練
基礎文件和目錄結構
首先,創建以下目錄和文件結構:
cross-origin-project/
│
├── public/
│ └── index.html
│
├── serverA.js
└── serverB.js
文件內容
- public/index.html - 這是服務 A 將托管的靜態 HTML 文件,用于發起跨域請求。
- serverA.js - 這個文件包含了服務 A 的代碼,用于托管靜態頁面。
- serverB.js - 這個文件包含了服務 B 的代碼,用于提供 API 并處理 CORS。
服務 A:提供靜態 HTML 頁面
服務 A 托管靜態 HTML 頁面,允許用戶通過按鈕觸發跨域請求。服務運行在端口 3001。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Cross-Origin Example</title>
<script>
async function fetchData() {try {const response = await fetch('http://localhost:3002/data', {method: 'GET',credentials: 'include', // 啟用憑證headers: {'X-Custom-Header': 'value' // 添加不被允許的頭部}});const data = await response.json();document.getElementById('data').textContent = JSON.stringify(data);} catch (error) {console.error('Error:', error);}
}
</script>
</head>
<body>
<h1>Cross-Origin Resource Sharing (CORS) Test</h1>
<button onclick="fetchData()">Fetch Data</button>
<p id="data"></p>
</body>
</html>
將以下 JavaScript 代碼保存到 serverA.js
文件中。這段代碼配置了一個 Express.js 服務器來托管 public
目錄下的靜態文件。
const express = require('express');
const app = express();// 提供靜態文件
app.use(express.static('public'));app.listen(3001, () => {console.log('Server A running on http://localhost:3001');
});
服務 B:提供 API 數據
將以下 JavaScript 代碼保存到 serverB.js
文件中。這段代碼創建了另一個 Express.js 服務器,它提供一個 API 端點并配置了 CORS 以允許來自服務 A 的跨域請求。
Node.js 配置(服務 B):
const express = require('express');
const cors = require('cors');
const app = express();// 配置 CORS 策略
app.use(cors({origin: 'http://localhost:3001', // 允許來自 3001 端口的跨域請求methods: ['GET'],credentials: true, // 允許跨域請求包含憑證信息allowedHeaders: ['Content-Type', 'X-Custom-Header'] // 明確允許自定義頭部
}));app.get('/data', (req, res) => {const allowedOrigins = ['http://localhost:3001'];const origin = req.headers.origin;if (allowedOrigins.includes(origin)) {res.json({ message: 'Successfully fetched cross-origin data from server B.' });} else {res.status(403).send('Access denied');}
});app.listen(3002, () => {console.log('Server B running on http://localhost:3002');
});
如何運行項目
- 打開命令行界面,并導航到
cross-origin-project
文件夾。 - 運行
npm init -y
來初始化 Node.js 項目,并通過npm install express cors
安裝必要的依賴。 - 在兩個不同的命令行窗口中,分別啟動服務 A 和服務 B:
- 運行
node serverA.js
啟動服務 A。 - 運行
node serverB.js
啟動服務 B。
- 運行
訪問 http://localhost:3001
在瀏覽器中查看 HTML 頁面,并嘗試發起跨域請求。此時應該能看到成功信息打印成功。
跨域原因列舉
CORS(跨域資源共享)設置可能因為多種原因而失敗,導致跨域請求無法成功。這些原因包括配置錯誤、環境限制或者瀏覽器的安全策略。下面是一些常見的原因導致跨域設置不成功:
1. 錯誤的源設置
如果 CORS 策略中設置的 Access-Control-Allow-Origin
頭不正確,如不匹配請求的源(origin),跨域請求將被拒絕。例如,如果服務器只允許來自 http://localhost:3001
的請求,但請求實際來自 http://127.0.0.1:3001
(盡管這兩個地址指向同一個位置,但對 CORS 來說它們是不同的源)。
2. 忽略發送憑證
如果在 AJAX 請求中設置了 withCredentials = true
,但服務器端的 CORS 策略沒有正確設置 Access-Control-Allow-Credentials: true
,則會導致跨域請求失敗。同時,Access-Control-Allow-Origin
不能設置為 *
當 Credentials
為 true
。
3. 不支持的請求方法
如果請求使用了 CORS 策略中未明確允許的 HTTP 方法(如 PUT、PATCH、DELETE 等),請求也會被拒絕。只有正確聲明了 Access-Control-Allow-Methods
頭部,才能支持額外的方法。
4. 不允許的頭部字段
如果跨域請求中包含了服務器未通過 Access-Control-Allow-Headers
明確允許的頭部字段,請求將被瀏覽器攔截。這常見于自定義頭部,如 X-Custom-Header
。
5. 預檢請求失敗
復雜請求(如帶自定義頭或者特定方法的請求)首先會發送一個預檢(OPTIONS)請求,如果預檢請求沒有得到正確處理(如未返回允許的方法或頭部),實際請求不會被發送。
6. 瀏覽器的安全策略
不同瀏覽器對于跨域請求的處理可能略有差異,有些瀏覽器的安全策略可能更加嚴格。此外,瀏覽器的某些配置或擴展程序(如廣告攔截器或安全插件)可能阻止跨域請求。
7. 網絡問題或服務器不可達
服務器配置正確,但由于網絡問題或服務器宕機,導致請求無法到達服務器或服務器無法正確響應。
8. 錯誤的端口或協議
有時開發人員可能忽視了端口或協議的差異,這些細微的差別也會導致跨域錯誤,因為 CORS 是對協議、域名和端口都敏感的。
跨域行為實戰
我們可以在先前給出的服務 A 和服務 B 的代碼基礎上,通過修改設置和觀察結果來測試各種導致 CORS 設置不成功的情況。以下是針對每種情況的修改方法和預期的測試結果:
1. 錯誤的源設置
修改服務 B 來只接受來自 http://127.0.0.1:3001
的請求,而我們的請求實際上來自 http://localhost:3001
。
修改后的服務 B (api.js):
const express = require('express');
const cors = require('cors');
const app = express();app.use(cors({origin: 'http://127.0.0.1:3001', // 錯誤的源設置methods: ['GET'],credentials: true
}));app.get('/data', (req, res) => {res.json({ message: 'Successfully fetched cross-origin data from server B.' });
});app.listen(3002, () => {console.log('Server B running on http://localhost:3002');
});
預期結果: 瀏覽器控制臺將顯示類似于 "No 'Access-Control-Allow-Origin' header is present on the requested resource" 的錯誤,表示請求的源不被允許。
2. 忽略發送憑證
刪除或錯誤設置 Access-Control-Allow-Credentials
,同時保留 credentials: 'include'
。
修改后的服務 B (api.js):
app.use(cors({origin: 'http://localhost:3001',methods: ['GET'],credentials: false // 錯誤地設置為 false
}));
預期結果: 瀏覽器控制臺會顯示錯誤,提示憑證不被允許,因為 credentials
屬性需要服務器明確允許。
3. 不支持的請求方法
假設我們嘗試發送一個 POST 請求,但服務 B 只允許 GET。
服務 A 發起 POST 請求 (public/index.html):
<script>
async function fetchData() {try {const response = await fetch('http://localhost:3002/data', {method: 'POST',credentials: 'include'});const data = await response.json();document.getElementById('data').textContent = JSON.stringify(data);} catch (error) {console.error('Error:', error);}
}
</script>
預期結果: 瀏覽器控制臺會顯示錯誤,指出使用了未被允許的方法。
4. 不允許的頭部字段
發送包含自定義頭部的請求,而服務 B 沒有允許這個頭部。
修改服務 A (public/index.html):
<script>
async function fetchData() {try {const response = await fetch('http://localhost:3002/data', {method: 'GET',credentials: 'include',headers: {'X-Custom-Header': 'value' // 添加不被允許的頭部}});const data = await response.json();document.getElementById('data').textContent = JSON.stringify(data);} catch (error) {console.error('Error:', error);}
}
</script>
預期結果: 瀏覽器控制臺將顯示錯誤,提示 "Request header field X-Custom-Header is not allowed"。
通過這些修改和測試,你可以更好地理解 CORS 設置可能出現的問題和如何解決這些問題。每次修改后,確保重啟服務并清空瀏覽器緩存,以確保測試結果的準確性。
服務端不做修改,瀏覽器如何避免cors
在 Web 開發中,瀏覽器的同源策略保護用戶免受惡意網站的攻擊,該策略禁止一個域的腳本與另一個域的資源進行交互。這意味著如果 CORS(跨源資源共享)策略不允許特定的跨源請求,那么在瀏覽器端通常沒有簡單的方法可以"避免"或"繞過" CORS 錯誤。這是一種安全機制,不應被繞過,特別是在生產環境中。
然而,對于開發測試目的,以下是幾種常用的方法來處理或繞過瀏覽器的 CORS 限制:
1. 使用代理服務器
在開發環境中,可以設置一個代理服務器,它接收你的請求,向目標服務器發送請求,并將響應返回給你的應用。這種方式常用于前端開發中,例如使用創建 React 應用的 create-react-app
工具,可以在 package.json
中添加一個代理配置:
"proxy": "http://localhost:3002",
這樣,所有對 /data
的請求將會被代理到 http://localhost:3002/data
,而瀏覽器會認為這些請求仍然是同源的。
2. 使用瀏覽器插件
有些瀏覽器插件可以禁用或修改 CORS 策略,例如 "CORS Unblock" 等。這些插件通常用于開發和測試,但絕對不推薦在生產環境或瀏覽常規網站時使用,因為這會降低你的網絡安全防護。
如何使用 CORS Unblock
步驟 1: 安裝擴展
-
Chrome 瀏覽器:
- 打開 Chrome Web Store。
- 在搜索框中輸入 “CORS Unblock”。
- 找到擴展,點擊 “添加至 Chrome”。
-
Firefox 瀏覽器:
- 打開 Firefox Add-ons 網站。
- 搜索 “CORS Unblock”。
- 選擇擴展并點擊 “添加到 Firefox”。
步驟 2: 使用擴展
安裝擴展后,你會在瀏覽器的工具欄中看到 CORS Unblock 的圖標。你可以通過以下方式使用它:
-
激活/禁用 CORS Unblock:
- 點擊瀏覽器工具欄中的 CORS Unblock 圖標。
- 通常,擴展會有一個簡單的開關按鈕,允許你快速激活或禁用跨源請求限制。
- 當擴展被激活時,圖標通常會變色(例如變為綠色),表示現在跨源請求的限制被禁用。
-
刷新頁面:
- 在激活 CORS Unblock 后,刷新你正在工作的網頁。現在,頁面上的腳本應該能夠發出并接收原本因 CORS 策略而被阻止的請求。
步驟 3: 監控和調試
- 使用瀏覽器的開發者工具(通常可以通過右鍵點擊頁面并選擇“檢查”來打開)來監控網絡請求和響應。
- 檢查網絡標簽,確保之前因 CORS 錯誤被阻止的請求現在是否成功。
3. 配置瀏覽器啟動參數
對于 Chrome 瀏覽器,可以通過添加啟動參數來禁用安全特性,這包括 CORS 檢查。可以使用以下命令啟動 Chrome(僅在本地開發時使用):
chrome.exe --disable-web-security --user-data-dir="C:/ChromeDevSession"
這將開啟一個新的 Chrome 會話,其中禁用了跨域安全檢查。注意,這種方法僅應在完全控制的開發環境中使用,且必須保證不會瀏覽任何潛在的惡意網站。
4. 修改服務器配置(理想方式)
雖然這不是瀏覽器端的解決方案,但修改服務器以發送正確的 CORS 頭部是解決跨域問題的最佳實踐。這包括正確配置 Access-Control-Allow-Origin
和相關的 CORS 頭部。這是最安全和最可靠的解決方案。