Same-Origin Policy(同源政策)

瀏覽器有一個安全規則:JavaScript 只能讀取和自己「同源」的資源

「同源」的定義:Scheme + Host + Port 完全一樣

URL AURL B是否同源
https://example.com/apphttps://example.com/api✓(path 不算)
https://example.comhttp://example.com✗(scheme 不同)
https://example.comhttps://api.example.com✗(host 不同)
https://example.comhttps://example.com:8080✗(port 不同)

為什麼要有這個限制?防止惡意網站的 JavaScript 讀取用戶登入其他網站的資料。如果沒有同源政策,你打開一個惡意網站,它的 JavaScript 就可以用你的 cookie 去呼叫銀行 API 讀取你的帳戶。


CORS 是什麼

CORS(Cross-Origin Resource Sharing)是讓 server 主動授權特定跨域請求的機制。

Server 在 response header 裡說「我允許來自 https://frontend.example.com 的請求」:

Access-Control-Allow-Origin: https://frontend.example.com

瀏覽器看到這個 header,才允許 JavaScript 讀取回應。

Access-Control-Allow-Origin: * 表示允許任何 origin,適合公開 API,不適合有 cookie / auth 的 API。


Preflight Request(預檢請求)

對於「非簡單請求」(non-simple request),瀏覽器在真正的 request 之前,先發一個 OPTIONS 請求詢問 server:「我能這樣請求嗎?」

什麼是簡單請求(不觸發 preflight):

  • Method: GET / POST / HEAD
  • Content-Type: application/x-www-form-urlencoded / multipart/form-data / text/plain
  • 沒有自定義 header

什麼觸發 preflight

  • Method: PUT / DELETE / PATCH
  • Content-Type: application/json
  • 自定義 header(如 AuthorizationX-Custom-Header
Preflight:
OPTIONS /api/users HTTP/1.1
Origin: https://frontend.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization

Server Response:
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization
Access-Control-Max-Age: 86400  ← preflight 結果可以快取多久(秒)

正確設定 CORS

Express(Node.js)

import cors from 'cors';
 
app.use(cors({
    origin: ['https://app.example.com', 'http://localhost:3000'],
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,  // 允許 cookie
    maxAge: 86400,      // preflight cache
}));

Djangodjango-cors-headers):

CORS_ALLOWED_ORIGINS = [
    "https://app.example.com",
    "http://localhost:3000",
]
CORS_ALLOW_CREDENTIALS = True

常見的 CORS 錯誤

  • 後端只設了 GET 允許,但前端送 POST(preflight 失敗)
  • credentials: true 但 server 沒有設 Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Origin: * 配合 credentials: true(瀏覽器拒絕,不允許這個組合)

重要:CORS 是瀏覽器安全機制,Server 仍然收到請求

CORS 的限制在瀏覽器端,server 仍然收到並處理了請求,只是瀏覽器不讓 JavaScript 讀取回應。這意味著:

  1. CORS 防的是瀏覽器裡的跨域讀取,不防 curl 或 server-to-server
  2. 即使 CORS 報錯,危險的操作(DELETE、PUT)可能已經在 server 執行了

需要真正的存取控制(Authentication + Authorization),CORS 只是一個補充的瀏覽器層保護。