本文 AI 產出,尚未審核

JavaScript

單元:Fetch 與網路請求(Networking)

主題:CORS 原理


簡介

在前端開發中,跨來源資源共享(Cross‑Origin Resource Sharing,簡稱 CORS) 是每一位使用 fetchXMLHttpRequest 或其他 AJAX 技術的開發者必須面對的概念。
瀏覽器為了保護使用者的資料安全,預設只允許同源(same‑origin)的請求;當前端程式需要向不同網域的 API 取得資料時,就會觸發 CORS 機制。

了解 CORS 的運作原理不僅可以避免「被瀏覽器阻擋」的尷尬錯誤,還能協助你在設計前後端介面時,正確設定 HTTP 標頭、避免安全漏洞,提升應用的可用性與可靠性。


核心概念

1. 同源政策(Same‑Origin Policy)

同源政策是瀏覽器的基礎安全模型,判斷「同源」的條件包括 協議(protocol)主機(host)埠號(port)
例如:

URL 協議 主機 埠號
https://example.com/api https example.com 443
http://example.com/api http example.com 80
https://api.example.com https api.example.com 443

上述三個 URL 中,只有第一個與 https://example.com 完全相同,才算是同源。

2. 為什麼會有 CORS

當前端要向 不同來源(例如前端在 https://myapp.com,而 API 在 https://api.partner.com)發送請求時,瀏覽器會先檢查回應的 CORS 標頭,決定是否允許這筆跨域請求。若標頭缺失或不符合規範,瀏覽器會直接拋出 CORS 錯誤,而不會把回應內容交給 JavaScript。

3. CORS 的三個主要標頭

標頭 作用 範例
Access-Control-Allow-Origin 指定允許的來源(* 代表所有) Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods 允許的 HTTP 方法(GET、POST、PUT…) Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers 允許的自訂請求標頭 Access-Control-Allow-Headers: Content-Type, Authorization

此外,Access-Control-Allow-Credentials 用於告訴瀏覽器是否允許傳送 Cookie、HTTP 認證資訊。若設定為 true,則 Access-Control-Allow-Origin 不能*,必須明確列出來源。

4. 預檢請求(Preflight Request)

對於 非簡單請求(simple request)——例如使用自訂標頭、使用 PUTDELETE、或 Content-Typeapplication/json 等情況,瀏覽器會先發送一個 OPTIONS 方法的 預檢請求,檢查伺服器是否接受這類請求。

預檢流程簡述:

  1. 瀏覽器送 OPTIONS /resource,帶上 OriginAccess-Control-Request-MethodAccess-Control-Request-Headers
  2. 伺服器回應 200,並附上 Access-Control-Allow-* 系列標頭。
  3. 若回應符合要求,瀏覽器才會發送真正的請求(如 POSTPUT)。

5. 簡單請求(Simple Request)

符合以下條件的請求不會觸發預檢:

條件 說明
方法 必須是 GETHEADPOST
標頭 只能是 AcceptAccept-LanguageContent-LanguageContent-Type(且 Content-Type 只能是 application/x-www-form-urlencodedmultipart/form-datatext/plain
無自訂標頭(如 X-My-Header

程式碼範例

下面提供 5 個實用範例,從最簡單的同源請求到完整的 CORS 設定,讓你一步步掌握概念。

範例 1:同源的 fetch(不涉及 CORS)

// 這是一個同源請求,瀏覽器直接允許
fetch('/api/user')
  .then(res => res.json())
  .then(data => console.log('User:', data))
  .catch(err => console.error('Error:', err));

說明:因為請求的 URL 與目前網頁同源,瀏覽器不會檢查 CORS 標頭。

範例 2:跨域的簡單 GET(會檢查 Access-Control-Allow-Origin

// 目標 API 位於 https://api.example.com
fetch('https://api.example.com/public-data')
  .then(res => {
    if (!res.ok) throw new Error('Network response was not ok');
    // 若 CORS 標頭不符合,這裡會直接拋出錯誤
    return res.json();
  })
  .then(data => console.log('Public data:', data))
  .catch(err => console.error('CORS error:', err));

伺服器端(Node.js Express 範例):

app.get('/public-data', (req, res) => {
  res.set('Access-Control-Allow-Origin', '*'); // 允許所有來源
  res.json({ message: 'Hello from API' });
});

範例 3:跨域的 POST,觸發預檢(Preflight)

fetch('https://api.example.com/submit', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json', // 非簡單的 Content-Type
    'X-Requested-With': 'XMLHttpRequest' // 自訂標頭
  },
  body: JSON.stringify({ name: 'Alice' }),
  credentials: 'include' // 想要帶上 Cookie
})
  .then(res => res.json())
  .then(result => console.log('Result:', result))
  .catch(err => console.error('CORS error:', err));

伺服器端(Express):

app.options('/submit', (req, res) => {
  // 回應預檢
  res.set({
    'Access-Control-Allow-Origin': 'https://myapp.com',
    'Access-Control-Allow-Methods': 'POST, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With',
    'Access-Control-Allow-Credentials': 'true',
    'Access-Control-Max-Age': '86400' // 預檢結果快取一天
  });
  res.sendStatus(204);
});

app.post('/submit', (req, res) => {
  // 真正的處理邏輯
  res.set({
    'Access-Control-Allow-Origin': 'https://myapp.com',
    'Access-Control-Allow-Credentials': 'true'
  });
  res.json({ success: true, data: req.body });
});

範例 4:使用 mode: 'cors'credentials 的細節

fetch('https://api.example.com/user-info', {
  method: 'GET',
  mode: 'cors',          // 明確告訴瀏覽器這是跨域請求
  credentials: 'include' // 允許攜帶 Cookie、HTTP 認證
})
  .then(res => res.json())
  .then(info => console.log('User info:', info))
  .catch(err => console.error('CORS error:', err));

重點

  • mode: 'cors' 為預設值,但在某些情況(如 no-cors)會限制可讀取的回應。
  • 若伺服器未回傳 Access-Control-Allow-Credentials: trueinclude 會被瀏覽器自動忽略。

範例 5:在前端偵測 CORS 錯誤並顯示友善訊息

async function getData() {
  try {
    const res = await fetch('https://api.partner.com/data');
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const json = await res.json();
    console.log(json);
  } catch (e) {
    if (e instanceof TypeError && e.message.includes('Failed to fetch')) {
      // 大多是 CORS 被阻擋
      alert('無法取得資料,可能是 CORS 設定有誤,請聯絡系統管理員。');
    } else {
      console.error(e);
    }
  }
}

說明TypeError: Failed to fetch 常見於 CORS 被拒絕,透過此方式可以在 UI 上給予使用者明確的提示。


常見陷阱與最佳實踐

陷阱 說明 最佳實踐
使用 * 搭配 credentials: 'include' 瀏覽器會直接拋出錯誤,因為 * 代表「任何來源」且無法傳送憑證。 若需要 Cookie,必須Access-Control-Allow-Origin 中寫明確的來源,且同時設定 Access-Control-Allow-Credentials: true
忘記回傳 Access-Control-Allow-Headers 自訂標頭未被允許,導致預檢失敗。 在伺服器端列出所有可能的自訂標頭,或使用 Access-Control-Allow-Headers: *(部分瀏覽器支援)。
預檢過於頻繁 每次跨域 POST 都會觸發 OPTIONS,增加延遲。 使用 Access-Control-Max-Age 設定快取時間(例如 86400 秒),降低預檢請求次數。
忽略 HTTPS 若前端是 HTTPS,跨域的 HTTP 端點會被瀏覽器視為不安全而阻擋。 確保 所有端點皆使用相同的協議,或在開發環境使用本機代理解決。
在前端硬編碼 mode: 'no-cors' 會讓回應變成 opaque(不可讀),常見於想「繞過」CORS 卻失去資料。 只在真的不需要讀取回應內容(如圖片、腳本)時使用,否則應正確設定伺服器 CORS。

實際應用場景

  1. SPA 與微服務
    現代單頁應用(React、Vue、Angular)常把 UI 與後端 API 分別部署在不同子域或不同雲端服務上。此時必須在每個微服務的入口(Gateway)加上正確的 CORS 設定,才能讓前端自由呼叫。

  2. 第三方 API 整合
    例如使用 Google Maps、Stripe、GitHub API 等外部服務。大部分服務已預先在伺服器端開啟 Access-Control-Allow-Origin: *,但若需要傳送認證資訊(Bearer Token),仍需確認是否支援 Access-Control-Allow-Credentials

  3. 跨域上傳檔案
    使用 FormData 透過 POST 上傳檔案時,Content-Type 會自動變為 multipart/form-data,屬於簡單請求。但若同時加上自訂標頭(例如 X-CSRF-Token),就會觸發預檢,開發者必須在伺服器端處理 OPTIONS

  4. 企業內部系統
    多個子系統(ERP、CRM、BI)可能在不同內網域上運行,透過 CORS 可以在不改動 DNS 或代理的情況下,讓前端一次性呼叫多個系統的 API。


總結

  • CORS 是瀏覽器為保護使用者而設的跨域安全機制,核心依賴 HTTP 標頭Access-Control-Allow-*)來決定是否允許請求。
  • 簡單請求 不會觸發預檢;非簡單請求(自訂標頭、非 GET/HEAD/POST、特殊 Content-Type)會先發送 OPTIONS 預檢。
  • Access-Control-Allow-Origin 必須正確對應來源;若要傳送憑證,Access-Control-Allow-Credentials 必須為 true,且來源不能是 *
  • 常見陷阱包括 *credentials 同時使用、忘記回傳允許的自訂標頭、預檢過於頻繁等。透過 快取 (Access-Control-Max-Age)明確列出來源使用 HTTPS 等最佳實踐,可大幅提升開發效率與安全性。

掌握 CORS 後,你就能在 FetchAxiosXMLHttpRequest 等工具中自信地與任何跨域 API 互動,為前端應用打造更穩定、可擴充的網路請求層。祝你開發順利!