0x00 引言
CORS 全稱是跨域資源共享(Cross-Origin Resource Sharing),是一種安全機制,用於限制哪些源(origin)可以訪問伺服器上的資源,這裡探討下該如何正確的設定 CORS。
TLDR
關鍵配置要點:
Access-Control-Allow-Origin
: 禁止使用*
,應該設定為具體的白名單域名Access-Control-Allow-Methods
: 明確指定允許的 HTTP 方法Access-Control-Allow-Headers
: 明確指定允許的請求頭Access-Control-Allow-Credentials
: 如需攜帶認證資訊,必須設定為true
0x01 跨域觸發條件
注:簡單請求的定義:
- 請求方法為 GET、POST 或 HEAD
- 請求頭只包含安全的欄位(Accept、Accept-Language、Content-Language、Content-Type等)
- Content-Type 只限於:application/x-www-form-urlencoded、multipart/form-data、text/plain
這裡只選取了部分內容,更多內容請參考 MDN 文件
0x02 CORS 規範
當為非同源且非簡單請求時會觸發 preflight
檢查,瀏覽器會先發出一個 OPTIONS
請求,用於檢查伺服器是否支援該請求,其請求頭資訊和正式請求一致,但不會攜帶Body。
🚨 重要提示: 為什麼特別強調非簡單請求?當瀏覽器認為這個請求為簡單請求時,不會透過兩次請求進行檢查,也就是沒有 OPTIONS
preflight 請求,是直接傳送正式請求,然後根據正式請求的返回頭判定是否跨域決定指令碼是否可以讀取返回內容。
因此也帶來一個問題,雖然前端讀取不到伺服器返回的資訊,但請求是真實發出去伺服器執行了的,如果伺服器沒有正確的跨域處理中介軟體則會導致安全問題。
Example:
OPTIONS /api/v1/user HTTP/1.1
Origin: http://hacker.com
Sec-Fetch-Dest: empty
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
伺服器在收到 OPTIONS
請求後,需要根據配置進行檢查,如果允許則返回以下資訊:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://hacker.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
0x03 設定建議
Origins
建立一箇中介軟體,在處理器處理之前檢查 Origin
和 Method
,首先判斷 Origin
是否在白名單內,如果在白名單允許範圍內再檢查 Method
,如果是 OPTIONS
請求則直接返回 204 No Content
,否則進入下一步繼續處理請求。
Methods
可以設定為 *
,如果需要限制則設定為白名單。
Headers
可以設定為 *
,推薦限制則設定為白名單。注意,在 Authorization
標頭不能被泛化處理,始終需要明確列出。如果伺服器還提供影片服務且為白名單策略時需要新增一個 Range
頭。
Credentials
如果設定為 true
,則允許瀏覽器在跨域請求中攜帶 Cookie
資訊,但同時 Access-Control-Allow-Origin
不能設定為 *
必須明確指出具體域名,如果設定為 *
會無效。
Expose-Headers
無所謂,可以不指定,如果瀏覽器需要訪問未在 Access-Control-Allow-Headers
中列出的頭可以透過設定這個頭來解決。
0x04 示例程式碼
const allowedOrigins = ['https://example.com', 'https://api.example.com'];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
特別注意:
- 預檢請求的快取:可透過
Access-Control-Max-Age
設定預檢請求的快取時間,避免重複傳送 - 錯誤處理:當 CORS 檢查失敗時,瀏覽器會在控制檯輸出詳細的錯誤資訊,但實際的網路請求響應會被瀏覽器攔截,JavaScript 無法訪問具體的錯誤資訊