Same-Origin Policy(同源政策)
瀏覽器有一個安全規則:JavaScript 只能讀取和自己「同源」的資源。
「同源」的定義:Scheme + Host + Port 完全一樣。
| URL A | URL B | 是否同源 |
|---|---|---|
https://example.com/app | https://example.com/api | ✓(path 不算) |
https://example.com | http://example.com | ✗(scheme 不同) |
https://example.com | https://api.example.com | ✗(host 不同) |
https://example.com | https://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(如
Authorization、X-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
}));Django(django-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: trueAccess-Control-Allow-Origin: *配合credentials: true(瀏覽器拒絕,不允許這個組合)
重要:CORS 是瀏覽器安全機制,Server 仍然收到請求
CORS 的限制在瀏覽器端,server 仍然收到並處理了請求,只是瀏覽器不讓 JavaScript 讀取回應。這意味著:
- CORS 防的是瀏覽器裡的跨域讀取,不防 curl 或 server-to-server
- 即使 CORS 報錯,危險的操作(DELETE、PUT)可能已經在 server 執行了
需要真正的存取控制(Authentication + Authorization),CORS 只是一個補充的瀏覽器層保護。