為什麼跨域請求會產生錯誤?如何處理?
為什麼跨域請求會產生錯誤?
跨域請求錯誤的根源在於瀏覽器的同源政策(Same-Origin Policy)。這是瀏覽器的一項安全機制,用來防止網頁從不同來源(domain、protocol 或 port)訪問彼此的資源,從而降低惡意攻擊的風險(例如跨站腳本攻擊 XSS)。
什麼是同源?
同源指的是以下三個條件必須相同:
-
協議(Protocol):例如 http 或 https。
-
域名(Domain):例如 example.com 或 api.example.com。
-
端口(Port):例如 80 或 443。
如果前端網頁(例如 http://localhost:3000 )試圖向不同來源的後端(例如 https://api.example.com )發送請求,瀏覽器會因為違反同源政策而阻止該請求,並在控制台顯示類似以下的錯誤訊息:
Access to XMLHttpRequest at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
這個錯誤表示後端伺服器沒有明確允許 http://localhost:3000 訪問它的資源。
為什麼會有 CORS 錯誤?
CORS 錯誤通常出現在以下情況:
-
後端未設置 CORS 頭部:後端伺服器沒有在回應中包含 Access-Control-Allow-Origin 頭部,告訴瀏覽器哪些來源可以訪問資源。
-
請求類型不被允許:例如,後端只允許簡單請求(GET、POST、HEAD),但你發送了非簡單請求(例如帶有自定義頭部或使用 PUT、DELETE 方法)。
-
預檢請求(Preflight Request)失敗:對於非簡單請求,瀏覽器會先發送一個 OPTIONS 請求檢查是否允許,後端若未正確回應,則會觸發錯誤。
-
不同源的請求:前端和後端的協議、域名或端口不同,觸發同源政策的限制。
如何處理跨域請求錯誤?
以下是幾種常見的解決方法,我會詳細說明每種方法,並提供完整的 JavaScript 程式碼範例,方便你操作。
1. 後端設置 CORS 頭部(推薦)
最常見且正確的解決方式是在後端伺服器設置 CORS 頭部,允許特定來源的請求。這種方式需要後端工程師配合修改伺服器配置。
後端設置範例(以 Node.js + Express 為例):
// 後端程式碼 (server.js)
const express = require("express");
const app = express();
// 設置 CORS,允許來自 http://localhost:3000 的請求
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "http://localhost:3000"); // 允許特定來源
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); // 允許的 HTTP 方法
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization"); // 允許的頭部
next();
});
// 範例 API
app.get("/api/data", (req, res) => {
res.json({ message: "這是跨域請求的資料!" });
});
app.listen(5000, () => {
console.log("伺服器運行在 http://localhost:5000");
});
前端請求程式碼:
// 前端程式碼 (index.js)
fetch("http://localhost:5000/api/data", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data) => {
console.log("成功取得資料:", data);
})
.catch((error) => {
console.error("請求失敗:", error);
});
說明:
-
後端設置 Access-Control-Allow-Origin 為 http://localhost:3000 ,允許該來源的請求。
-
如果想允許所有來源,可以設置 Access-Control-Allow-Origin: *,但這在安全性上不推薦,特別是涉及敏感資料時。
-
對於非簡單請求,後端需要處理 OPTIONS 預檢請求。
操作步驟:
-
請後端工程師在伺服器上添加上述 CORS 配置。
-
確認前端請求的 URL 和後端 API 的 URL 是否正確。
-
在瀏覽器中測試請求是否成功。
2. 使用代理伺服器(前端開發時常用)
在開發階段,如果後端尚未設置 CORS,可以在前端專案中使用代理伺服器,將跨域請求轉發到後端,這樣瀏覽器認為請求是同源的。
以 Create React App 為例:
-
在 package.json 中添加代理配置:
{
"proxy": "http://localhost:5000"
} -
前端發送請求時,使用相對路徑:
// 前端程式碼 (index.js)
fetch("/api/data", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data) => {
console.log("成功取得資料:", data);
})
.catch((error) => {
console.error("請求失敗:", error);
});
說明:
-
Create React App 的開發伺服器會將 /api/data 轉發到 http://localhost:5000/api/data。
-
這種方式僅適用於開發環境,上線後仍需後端設置 CORS 或其他解決方案。
操作步驟:
-
在 package.json 中添加 proxy 字段,指向後端伺服器的地址。
-
確保後端伺服器正在運行。
-
使用相對路徑發送請求,測試是否成功。
3. 使用 JSONP(不推薦,僅適用於 GET 請求)
JSONP(JSON with Padding)是一種舊的跨域解決方案,通過動態創建 <script>
標籤來繞過同源政策。但由於安全性和限制(僅支援 GET 請求),現在很少使用。
前端程式碼:
// 前端程式碼 (index.js)
function handleJSONPResponse(data) {
console.log("JSONP 回應資料:", data);
}
const script = document.createElement("script");
script.src = "http://localhost:5000/api/data?callback=handleJSONPResponse";
document.body.appendChild(script);
後端程式碼(以 Node.js + Express 為例):
// 後端程式碼 (server.js)
const express = require("express");
const app = express();
app.get("/api/data", (req, res) => {
const callback = req.query.callback;
res.send(`${callback}(${JSON.stringify({ message: "這是 JSONP 資料!" })})`);
});
app.listen(5000, () => {
console.log("伺服器運行在 http://localhost:5000");
});
說明:
-
JSONP 通過
<script>
標籤載入資料,後端將資料包裝在指定的回呼函數中。 -
僅適用於 GET 請求,且有安全風險(例如可能被注入惡意程式碼)。
操作步驟:
-
後端需要支援 JSONP 格式的回應。
-
前端動態創建
<script>
標籤並指定回呼函數。 -
測試是否能正確接收資料。
4. 使用伺服器端代理(上線環境推薦)
如果後端無法修改 CORS 配置,可以在前端和後端之間設置一個代理伺服器,將請求轉發到目標 API。這個代理伺服器可以是 Node.js、Nginx 或其他伺服器。
以 Node.js 代理為例:
// 代理伺服器程式碼 (proxy.js)
const express = require("express");
const axios = require("axios");
const app = express();
app.get("/proxy/api/data", async (req, res) => {
try {
const response = await axios.get("https://api.example.com/data");
res.json(response.data);
} catch (error) {
res.status(500).json({ error: "代理請求失敗" });
}
});
app.listen(3000, () => {
console.log("代理伺服器運行在 http://localhost:3000");
});
前端程式碼:
// 前端程式碼 (index.js)
fetch("http://localhost:3000/proxy/api/data", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data) => {
console.log("成功取得資料:", data);
})
.catch((error) => {
console.error("請求失敗:", error);
});
說明:
-
代理伺服器運行在與前端同源的地址(例如 http://localhost:3000 ,因此不會觸發 CORS 錯誤。
-
代理伺服器負責向目標 API 發送請求並返回結果。
操作步驟:
-
安裝 Node.js 和 axios(npm install axios)。
-
運行代理伺服器程式碼。
-
前端向代理伺服器發送請求,測試是否成功。
5. 其他方法(不常見)
-
修改瀏覽器設定(僅限測試):在本地開發時,可以禁用瀏覽器的同源政策(例如 Chrome 使用 --disable-web-security 參數),但這僅限於測試,絕對不可用於上線環境。
-
WebSocket:如果 API 支援 WebSocket,可以用 WebSocket 進行跨域通信,因為 WebSocket 不受同源政策限制。
常見問題與解決建議
-
錯誤訊息顯示「No 'Access-Control-Allow-Origin' header」:
-
確認後端是否正確設置了 CORS 頭部。
-
如果無法修改後端,考慮使用代理伺服器。
-
-
預檢請求(OPTIONS)失敗:
-
檢查後端是否正確回應 OPTIONS 請求,並包含必要的 CORS 頭部。
-
確保請求的頭部和方法在 Access-Control-Allow-Headers 和 Access-Control-Allow-Methods 中允許。
-
-
開發環境正常,上線後失敗:
-
確認上線環境的代理配置或後端 CORS 設置是否正確。
-
檢查前端和後端的域名、協議和端口是否匹配。
-
總結
-
最佳實務:請後端設置 CORS 頭部,這是解決跨域問題最安全且標準的方式。
-
開發階段:使用代理伺服器(例如 Create React App 的 proxy)快速解決問題。
-
上線環境:考慮部署自己的代理伺服器或使用 CDN 提供的代理服務。
-
避免使用 JSONP:除非 API 僅支援 JSONP,否則應避免使用,因其安全性較低。