接上篇文章説道,跨域解決方案中的 CORS 方案,會配置一個 Access-Control-Allow-Origin 的配置項,而且我們一般不直接配置為 *,這樣做的原因是什麼以及企業中的最佳實踐是怎麼樣的,這篇文章給你答案!

簡單概括

  • Access-Control-Allow-Origin: *允許任意來源的頁面讀取你的響應內容​(對瀏覽器端的 JS 可讀性開放)。
  • ​**如果響應包含敏感數據或依賴 cookie/憑證(Authorization / session)**​,絕不能*。因為 *Access-Control-Allow-Credentials: true 不能同用,瀏覽器也會拒絕這種組合;但即便沒有 credentials,開放讀取會增加數據泄露與濫用風險。
  • 對於​公共靜態資源(可公開的圖片、腳本、樣式)​,用 * 是可接受且常見的做法;但對​API/用户數據/鑑權資源​,企業級環境會嚴格限定 origin 並配合其他安全措施。

* 的具體風險

  1. 數據暴露風險任意第三方站點都能通過 AJAX 獲取你的響應並在用户瀏覽器內讀取到響應數據(如果該請求不需要 cookie)。

  2. 無法與 credentials 一起用如果你的 API 依賴 Cookie / HTTP Auth(credentials: 'include'),你不能設置 *。瀏覽器會阻止讀取響應。

    這裏我説的 credentials: 'include'fetch​ 請求選項裏的一個配置,用來控制 ​**是否在跨域請求中攜帶憑證(如 Cookie、HTTP Auth(Authorization 請求頭)、Client SSL 證書)**​。

    當然,我們之前説過的,跨域訪問的話,服務端同時需要支持 Access-Control-Allow-Credentials: true

  3. 緩存污染與中間件問題如果你對 Origin 動態回顯而未設置 Vary: Origin,中間緩存(CDN、代理)可能將一個 origin 的響應誤發給另一個 origin,造成安全/隱私漏洞。

    1. Origin 是瀏覽器在 跨域請求 時自動攜帶的 ​請求頭​。比如:Origin:https://frontend.com
    2. Vary 是服務器的 ​緩存控制響應頭​。告訴緩存系統(如 CDN、瀏覽器緩存、負載均衡代理),當緩存這個響應時,需要 區分哪個請求頭不同 時生成不同的緩存版本。比如:Vary: Origin,不同 Origin 來訪問相同 URL,緩存系統要為他們分別緩存。

詳述 Origin 和 Very

鑑於很多同學在工作中,並沒有非常關注過這些配置,這裏再詳細講講。

先看常見的動態回顯代碼:res.setHeader("Access-Control-Allow-Origin", req.headers.origin);

這表示 後端根據請求方的 Origin 動態設置 CORS 允許域名。

但是 ​**如果不設置 ​Vary: Origin**​:

緩存(如 CDN 或代理服務器)可能把第一個請求的響應緩存下來。舉例:

  1. 用户 A(域名 a.com)訪問 → 緩存:Access-Control-Allow-Origin:https://a.com
  2. 用户 B(域名 b.com)訪問 → 本應得到:Access-Control-Allow-Origin:https://b.com
  3. 但由於緩存未區分,響應變成:Access-Control-Allow-Origin:https://a.com

=> 跨域安全事故:b.com​ 拿到了 ​a.com​ 的權限配置緩存

正確做法:用白名單 + Vary

const allowList = ["https://a.com", "https://b.com"];

const origin = req.headers.origin;
if (allowList.includes(origin)) {
  res.setHeader("Access-Control-Allow-Origin", origin);
  res.setHeader("Access-Control-Allow-Credentials", "true");
  res.setHeader("Vary", "Origin");   // 關鍵
}

企業推薦做法

  1. 返回​具體 origin(白名單)或動態回顯但要做嚴格白名單校驗​。必要時返回 Access-Control-Allow-Credentials: true 並確保 Access-Control-Allow-Origin 為允許的具體域名而非 *。如果必須動態回顯 Origin:​務必設置 ​Vary: Origin
  2. 對​**公共靜態資源(圖標、CDN 上的公開腳本、字體)**​:可以使用 *。例如圖片、CSS、JS(僅代表不包含敏感邏輯)通常可以 *,便於 CDN 與第三方站點直接引用。
  3. 不要僅在客户端身份驗證與鑑權,客户端防“君子”不防“小人”,必須​在服務器端強制檢查。​不能把鑑權依賴於 CORS;CORS 只是瀏覽器的便利機制。對每個請求在後端都做 token/session 驗證和權限檢查。
  4. Cookie 策略與 SameSite(配合 CORS 使用),對跨站登錄場景優先使用 SameSite=None; Secure,並在前端使用 fetch(..., credentials: 'include')。但前提是 Access-Control-Allow-Origin 不能為 *
  5. 使用反向代理,將前端請求同域發到 Nginx(配置同上述示例中的服務端配置),然後由 proxy 轉發到內部 API。這樣避免 CORS 的複雜性並能統一做鑑權、限流、審計。

詳述 SameSite=None; Secure

SameSite 是瀏覽器對 cookie 跨站發送 的限制策略,用來防止 CSRF 等跨站攻擊。SameSite 有三種值:

比如:Set-Cookie: session_id=xxxx; SameSite=None; Secure; HttpOnly; Path=/

  • Secure:cookie 只能在 HTTPS 的環境中發送,而且現代瀏覽器必須要求:SameSite=None必須同時帶 Secure 。
  • HttpOnly:客户端 JS 無法讀 cookie,防止 XSS 。
  • Path=/ :這個 Cookie ​在整個網站(所有路徑)都有效​。比如,我們的網站是 example.com ,瀏覽器在 example.com 域下自動設置了 Cookie(Set-Cookie 已響應),之後只要訪問 example.com 這個域下的任意路徑,都會自動帶上它。
    • 比如:Set-Cookie: token=abc123; Path=/user,訪問 /user/xxx 可以訪問,但是訪問 //api 等等的請求路徑就不會攜帶 token 。

寫到這裏,有些同學就想問了,這麼看來,cookie 挺好的呀,為什麼後面逐漸又出現了 jwt 這種會話控制方案呢?他們的區別是什麼呢?下一篇文章給你答案!