Stories

Detail Return Return

聊一聊Cookie的一些問題 - Stories Detail

Cookie 是站點為了保存訪客的一些信息,用來區分用户或者傳遞信息。在 Session 出現之前,基本上所有的網站都採用 Cookie 來區分用户身份。

Web 應用程序是使用 HTTP 協議傳輸數據的。HTTP 協議是無狀態的協議。一旦數據交換完畢,客户端與服務器端的連接就會關閉(HTTP 1.0 及以前),再次交換數據需要建立新的連接。Cookie 是一種是在客户端保持 HTTP 狀態的方案,用來唯一標識一個用户,同時記錄該用户的狀態。

Cookie 主要用於以下三個方面:

  • 會話狀態管理(如用户登錄狀態、購物車、遊戲分數或其它需要記錄的信息);
  • 個性化設置(如用户自定義設置、主題等);
  • 瀏覽器行為跟蹤(如跟蹤分析用户行為等)。

注意:Cookie 功能需要瀏覽器的支持。

如果瀏覽器不支持 Cookie 或者把 Cookie 禁用了,Cookie 機制就會失效。比如,Chrome 啓用或禁用:在右上角“更多”圖標 -> 設置 -> 隱私設置和安全性 -> 網站設置 -> Cookie 和網站數據。

Cookie是什麼?

從本質上説,Cookie 就是存儲在用户主機瀏覽器中的一小段文本文件。Cookies 是純文本形式,它們不包含任何可執行代碼,就其本身而言不是有害。一個 Web 頁面或服務器告之瀏覽器來將這些信息存儲並且基於一系列規則在之後的每個請求中都將該信息返回至服務器,然後 Web 服務器可以利用這些信息來標識用户。

  • Chrome 的 Cookie 數據文件位於:%LOCALAPPDATA%\Google\Chrome\User Data\Default\ Cookies。如:C:\Users\[whoami]\AppData\Local\Google\Chrome\User Data\Default\Cookies

    在用户[whoami]文件夾中,AppData通常是隱藏的。window 10下,點開查看 -> 勾中【隱藏的項目】。

  • Chrome 開發者工具中查看:F12 -> Application -> Cookies -> [website]
  • Chrome 設置中查看:在右上角“更多”圖標 -> 設置 -> 隱私設置和安全性 -> 網站設置 -> Cookie 和網站數據 -> 所有 Cookie 和網站數據。

Cookie工作原理

Cookie 使用 HTTP Header 傳遞數據。

Cookie機制定義了兩種報頭:Set-CookieCookieSet-Cookie 包含於 Web 服務器的響應頭中,Cookie 包含在客户端請求頭中。

Cookie 的運行過程具體分析如下:

  • 客户端在瀏覽器的地址欄中鍵入 Web 服務器的 URL,瀏覽器發送讀取網頁的請求。
  • 服務器接收到請求後,產生一個 Set-Cookie 信息,放在響應頭中一起回傳客户端,發起一次會話。
  • 客户端接到響應後,若要繼續該次會話,則將 Set-Cookie 中的內容取出,形成一個 Cookies 文件儲存在客户端計算機裏。
  • 當客户端再次向服務器發出請求時,瀏覽器先在電腦裏尋找對應該網站的 Cookies 文件。如果找到,則根據此 Cookies 產生 Cookie 信息,放在 HTTP 請求頭中發給服務器。這些是瀏覽器自動做的,而且每一次 HTTP 請求都會自動帶上 Cookie。
  • 服務器接收到包含 Cookie 信息的請求,檢索 Cookie 中與用户有關的信息,生成一個客户端所請示的頁面響應傳遞給客户端。

Cookie的屬性

Cookie 由一個名稱(name)、一個值(value)和其它幾個用於控制 Cookie 有效期、安全性、使用範圍的可選屬性組成,但對服務端而言,它只需要 name=value 值

Cookie 屬性名稱不區分大小寫,比如:DOMAIN 和 domain、secure 和 SECURE。

Cookie 的 name 值一般是區分大小寫的,但有些語言可能進行了封裝,也可不區分大小寫。比如:

  • ASP:response.cookies("aa") response.cookies("AA") 是指的同一個 Cookie。
  • JavaScript:document.cookie = "aa=1"document.cookie = "AA=2" 是兩個 Cookie。

服務端可以設置所有的 Cookie 屬性,但客户端有部分屬性是無法設置的,如,HttpOnly:

# 客户端
[name]=[value];expires=[new Date()];domain=[domain];path=[path];Secure;

# 服務端
[name]=[value];expires=[new Date()];domain=[domain];path=[path];Secure;HttpOnly,size=[size];SameSite=[Strict|Lax|none];Priority=[Medium]

name

字符串,用於唯一確定一個 Cookie 的名稱,在 JavaScript 中是區分大小寫的。

name 屬性是一個字符串,具體規則與 JavaScript 變量名稱有所不同:使用字母數字和下劃線、避免使用 $,沒有保留字或變量名限制。要真正理解命名規則,請閲讀HTTP 1.1標準。

value

字符串,用於存儲 Cookie 的值。

value 值中逗號、分號、空格被當做了特殊符號,所以有這三個特殊字符時,需要對進行額外編碼,如: encodeURI|encodeURIComponent

注意: value 值並不是一定要編碼。原始的文檔中指示僅有三種類型的字符必須進行編碼:分號、逗號、和空格

expires(未來某個時刻過期)

GMT 格式的時間,用於設置失效日期,默認有效期為:session(會話 Cookie,即瀏覽器關閉後過期)。

GMT 格式的時間,可通過 new Date().toGMTString() 或者 new Date().toUTCString() 獲取。JavaScript Date的使用和日期時間字符串格式

以下表示一天後失效:

// 正確寫法
// 注:時區原因,以下四種寫法表示的過期時間有差異

// Tue May 03 2022 22:48:41 GMT+0800 (中國標準時間)
document.cookie =`aaa=1234;expires=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000)};`
document.cookie =`aaa=1234;expires=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toString()};`

// Tue, 03 May 2022 14:48:41 GMT
document.cookie =`aaa=1234;expires=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toGMTString()};`
document.cookie =`aaa=1234;expires=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toUTCString()};`

如果設置的值無效,相當沒有設置 expires:

// 錯誤寫法
document.cookie = `aaa=1234;expires=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toISOString()};`
document.cookie = `aaa=1234;expires=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toLocaleString()};`

max-age(經過一定秒數過期)

數值(以秒為單位),表示在多少秒後失效,默認值是 -1(會話 Cookie,即瀏覽器關閉後過期)。

max-age 可接收的值:

  • 負數:會話 Cookie,即瀏覽器關閉後過期;
  • 0:表示刪除 Cookie;
  • 正數:有效期為 客户端收到 Cookie 的時刻 + max-age(秒)

以下表示一天後失效:

// 2022-05-03T15:02:08.782Z
document.cookie = `aaa=1234;max-age=${24 * 60 * 60};`

domain

字符串,表示允許訪問 Cookie 的域名,默認為當前文檔的域名。

需要注意以下問題:

  • Cookie不可跨站訪問:zhao.com 域名的 Cookie 不能被 example.com 域名訪問。這是由 Cookie 的隱私安全機制決定的。
  • name 相同但 domain 不同的是兩個不同的 Cookie。 如果想要兩個域名完全不同的網站共有 Cookie,可以生成兩個Cookie,domain 屬性分別為兩個域名,輸出到客户端。
  • domain 不能是頂級域名,即不能設置為 .comcom

設置 zhao.com 域名及其子域名允許訪問 Cookie:

document.cookie = 'aaa=1234;domain=.zhao.com;'

path

字符串,表示允許訪問 Cookie 的路徑,默認為 '/',即對當前域名下的所有路徑都有效。

設置 Path=/aaa,那麼只有 /aaa 及其子路徑的請求才會攜帶這個Cookie。

document.cookie = 'aaa=1234;path=/aaa;' // 只允許 /aaa 目錄下的頁面訪問 Cookie
document.cookie = 'aaa=1234;path=/;'    // 允許所有路徑訪問 Cookie

注意: name 相同但 path 不同,是兩個不同的 Cookie。

注意: cookie 在 Header 的順序,RFC 推薦在瀏覽器能夠按 path 的長短排序,越長説明匹配的越精確,順序越靠前。但並不是所有的瀏覽器都遵守這個,並且服務器也不應該依賴於 Cookie 出現的順序。

httponly

布爾值,表示 Cookie 是否允許客户端的 JavaScript(如,document.cookie)訪問,默認為空,即允許客户端通過 Javascript 訪問(讀、改、刪等)Cookie。

這主要用於緩解跨站腳本(XSS)攻擊,防止惡意腳本竊取敏感Cookie(如Session ID)。

注意: 客户端不能用 Javascript 設置帶 httponly 屬性的 Cookie(Chrome 開發者工具中可以設置),只能通過服務端來設置。

`httpOnly;` //不區分大小寫

secure

布爾值,表示是否僅允許通過 HTTPS 協議的請求攜帶,而不允許 HTTP 協議的請求攜帶,默認為空(即不管是 HTTPS 協議還是 HTTP 協議,都允許攜帶)。

這是保護 Cookie 不被中間人攻擊的重要安全措施。

document.cookie = 'aaa=1234;Secure;' // secure 不區分大小寫

注意: secure 屬性,只能在 HTTPS 協議下使用,HTTP 協議下不能設置 secure 屬性。

sameSite

字符串(枚舉值),表示是否允許 Cookie 在跨站請求中攜帶,默認值 Lax(在現代瀏覽器中,不同瀏覽器可能有差異)。

這是一個非常重要的安全屬性,用於防禦 CSRF 攻擊。

SameSite 可接收值有:

  • Strict:最嚴格,瀏覽器只會在同一站點的請求中發送 Cookie(即請求的域名和 Cookie 的域名完全一致)。
  • Lax:默認值(在現代瀏覽器中)。大多數情況也是不發送第三方 Cookie,但在安全(如HTTPS)的頂級導航(例如點擊鏈接)或GET請求中會攜帶 Cookie。
  • None:允許在跨站請求中發送Cookie。但前提是必須同時設置 Secure 屬性(即 Cookie 必須通過 HTTPS 傳輸)。
請求類型 示例 Strict Lax None
鏈接 <a href="..."></a> 不發送 發送 Cookie 發送 Cookie
預加載 <link rel="prerender" href="..."/> 不發送 發送 Cookie 發送 Cookie
GET 表單 <form method="GET" action="..."> 不發送 發送 Cookie 發送 Cookie
POST 表單 <form method="POST" action="..."> 不發送 不發送 發送 Cookie
iframe <iframe src="..."></iframe> 不發送 不發送 發送 Cookie
AJAX $.get("...") 不發送 不發送 發送 Cookie
Image <img src="..."> 不發送 不發送 發送 Cookie

注意以下問題:

  • SameSite=none 只能在 HTTPS 協議下設置。因為,設置 SameSite 屬性,Cookie 必須有 Secure 屬性,而 Secure 屬性只能在 HTTPS 協議下使用。
  • 有部分瀏覽器不能加 SameSite=none。比如:IOS 12 的 Safari 以及老版本的一些 Chrome 會把 SameSite=none 識別成 SameSite=Strict,所以服務端必須在設置 Set-Cookie 響應頭時進行 User-Agent 檢測,對這些瀏覽器不返回 SameSite=none 屬性。

注意: 2020 年 2 月份發佈的 Chrome 80 版本中默認屏蔽了第三方的 Cookie,即,之前默認是 None 的,Chrome 80 後默認是 Lax。

注意: 為解決 Chrome 瀏覽器中,Cookie 無法跨站使用,可以讓服務端設置 Cookie 的屬性 SameSite=none,或者使用 nginx 類的代理器統一設置 。

size

數值,表示 Cookie 在磁盤上佔用的總字節數

size 是 Name + Value + 所有其他屬性(如 Domain, Path, Expires 等)的序列化字符串的總長度,其作用是為幫助開發者瞭解 Cookie 的存儲開銷。瀏覽器對每個域名下的 Cookie 數量和總大小都有限制,這個屬性有助於進行調試和優化。

注意: 開發者不能設置/修改 size 的值。這是一個計算結果,由瀏覽器根據你設置的 Cookie 內容自動計算得出。

priority(Chrome 歷史遺留屬性)

字符串(枚舉值),用於在Cookie數量或大小達到瀏覽器限制時,決定哪些Cookie應該被優先保留

priority 的可選值:

  • Low
  • Medium (默認值)
  • High

當需要清理 Cookie 時,優先級為 Low 的 Cookie 會先於 MediumHigh 的被刪除。

注意: 開發者可能設置/修改 priority 屬性的值,但不推薦且不通用。這是一個非標準屬性,最初是 Chrome 特有的,其他瀏覽器可能不支持,並且現代 Chrome 中的清理算法可能已經發生了變化,依賴它並不可靠。

讀寫Cookie

服務器發送給客户端(瀏覽器)時,可以通過 Set-Cookie 創建、更新或刪除 Cookie;而客户端也可以通過瀏覽器內置的一些腳本,比如 Javascript,創建、更新或刪除 Cookie。

客户端

客户端可以設置 Cookie 的一些屬性:name、value、expires/max-age、domain、path、secure(HTTPS 協議下),但不能設置 HttpOnly 屬性。

創建 Cookie:

// 正確寫法:
document.cookie = `aaa=1234;domain=;path=/;max-age=${60 * 60};Secure;SameSite=none;Priority=Medium`

<span style="color: #0aa; font-weight: bold;">一次只能設置一個cookie。</span>

// 錯誤寫法
document.cookie = "aaa=1234; bbb=1234; ccc=1234";

如上同時設置多個 Cookie,實際只會添加第一個 aaa=1234,後面的 Cookie 都沒有添加成功。

最簡單的設置多個 Cookie 的方法就在重複執行 document.cookie = "[name]=[value]"

查看 Cookie:

document.cookie

修改 cookie: 重新賦值就行,舊的值會被新的值覆蓋。

document.cookie = `aaa=5678;domain=;path=/;expires=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000)};Secure;SameSite=none;Priority=Medium`

注意name、domain、path 都相同,Cookie 才會被覆蓋;否則是新增一個 Cookie。

刪除 Cookie: 一種方法是通過瀏覽器開發者工具清除 Cookie(有第三方的工具,瀏覽器自身也有這種功能);二是通過 Javascript 設置 Cookie 的有效期來清除 Cookie,如將 expires 屬性的值設置為一個過去的時間點或者 max-age 屬性值設置為 0。

注意:name、domain、path 都相同,Cookie 才會被刪除

document.cookie = 'aaa=5678;domain=;path=/;max-age=0'

服務端

服務器端通過響應頭的 Set-Cookie 管理瀏覽器的 Cookie。

服務器向客户端傳遞 Cookie,瀏覽器接收後會將 Cookie 儲存下來,以後每次發送請求時,瀏覽器發送請求的請求頭部會帶上 Cookie,但不是發送 Cookie 的所有屬性,而是只發送 name 和 value 值。

設置 Cookie: 一個 set-Cookie 字段只能設置一個 Cookie。當要設置多個 Cookie,需要添加多個 set-Cookie 字段。服務端可以設置 Cookie 的所有屬性選項:name、value、expires/max-age、domain、path、secure、HttpOnly。

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  Cookie cookie = new Cookie("name", "value");
  cookie.setComment("Web Host Name");
  cookie.setMaxAge(24 * 60 * 60);
  cookie.setPath("/");
  ...
  response.addCookie(cookie);
}

讀取 Cookie:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  Cookie[] cookies = request.getCookies();

  // 處理輸出中文亂碼問題
  response.setCharacterEncoding("UTF-8");
  response.setContentType("text/html;charset=UTF-8");

  for (Cookie cookie:cookies) {
    out.println("名稱:"+ cookie.getName() +"/n");
    out.println("值:"+ cookie.getValue() +"/n");
    out.println("有效時間:"+ cookie.getMaxAge() +"/n");
    out.println("域名:"+ cookie.getDomain() +"/n");
    out.println("路徑:"+ cookie.getPath() +"/n");
  }
}

刪除 Cookie:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  Cookie[] cookies = request.getCookies();
  for (Cookie cookie: cookies) {
    cookie.setMaxAge(0);
    response.addCookie(cookie);
  }
}

Cookie的特性

瀏覽器管理

瀏覽器對 Cookie 的管理是一個自動化的、基於規則的系統,主要包括四個環節:接收、存儲、發送和清理

  • 接收:接收服務器的 HTTP 響應時

    • 解析 Set-Cookie 頭部;
    • 根據規則驗證 Cookie 是否有效(比如:是否在允許的域和路徑下) ;
    • 有效,則將其存儲。
  • 存儲:Cookie 存儲在客户端(用户電腦)的特定文件或數據庫中,每個 Cookie 都與其域名、路徑等屬性一起存儲,每個域名維護一個獨立的 Cookie 塊。
  • 發送:客户端向服務器發起請求時,瀏覽器會執行 “Cookie匹配” 算法

    • 檢查域名是否匹配(包括子域);
    • 檢查路徑是否匹配(前綴匹配) ;
    • 檢查 Secure 標誌(如果設置了,則請求必須使用 HTTPS 協議) ;
    • 檢查 SameSite 標誌(根據當前請求是跨站還是同站來決定是否發送 Cookie);
    • 檢查 Cookie 是否已過期。
    • 所有匹配且未被阻止的 Cookie,組合成一個字符串,放在請求的 Cookie 頭部中發送給服務器。
  • 清理

    • 會話Cookie:當用户關閉瀏覽器時,這些 Cookie 會被清除。
    • 持久Cookie:當 Cookie 到達其 ExpiresMax-Age 指定的時間時,會被自動清除。
    • 手動清理:用户可以通過瀏覽器設置手動清除所有或特定站點的 Cookie。
    • 達到限制:當某個域名的 Cookie 數量或總大小超出瀏覽器限制時,瀏覽器會按照一定策略(如 LRU,最近最少使用)清理舊的 Cookie。

備註LRU 的全稱是 Least Recently Used,中文翻譯為最近最少使用

Cookie安全性

Cookie的安全性需要服務器和瀏覽器共同維護,主要通過設置正確的屬性來實現。

安全措施 作用 如何設置
Secure 屬性 只能通過HTTPS加密連接傳輸,防止在明文中被竊聽。 Set-Cookie: sid=abc123; Secure
HttpOnly 屬性 阻止 JavaScript 通過 document.cookie API 訪問該 Cookie,有效緩解 XSS 攻擊。 Set-Cookie: sid=abc123; HttpOnly
SameSite 屬性 控制跨站請求中是否被髮送,是防禦CSRF攻擊的利器。 `Set-Cookie: sid=abc123; SameSite=Strict Lax None` (None需配Secure)
合理的 Domain & Path 限制 Cookie的作用範圍,避免不必要的暴露 精確設置到需要的子域和路徑。
較短的過期時間 設置合理的過期時間

Cookie的優缺點

  • 優點

    • 簡單通用:技術非常成熟,所有瀏覽器都支持,服務器和客户端實現都簡單。
    • 狀態保持:解決了 HTTP 協議無狀態的問題,是實現會話管理、用户登錄的核心機制。
    • 無需服務器存儲:數據存儲在客户端,減輕了服務器的存儲壓力(但服務器仍需維護會話狀態)。
    • 自動管理:瀏覽器自動完成 Cookie 的發送和接收,對開發者透明。
  • 缺點

    • 容量和數量限制:每個 Cookie 大小通常只有 4KB,且每個域名下的 Cookie 數量有限。所有超出該限制的 Cookies 都會被截掉並且不會發送至服務器。
    • 性能開銷每次請求都會自動攜帶符合條件的 Cookie,如果 Cookie 過多過大,會增加網絡帶寬的消耗。
    • 安全性問題:容易受到 CSRF 和 XSS 攻擊,必須通過正確設置屬性來防護。
    • 隱私問題:第三方 Cookie 可用於追蹤用户在不同網站間的行為,因此正被現代瀏覽器逐步淘汰。
    • 客户端不可靠:用户可能禁用 Cookie,或者隨時手動清除它們。
    • 自動清理機制不確定:大小或數量超過時,IE 和 Opera 會刪除最近最少使用過的 Cookie,但是 Firefox 是隨機決定要清除哪個 Cookie。

Cookie限制

限制通常分為兩個方面:單個Cookie的大小每個域名的Cookie總數

瀏覽器 單個Cookie大小限制 每個域名Cookie總數限制 備註
Chrome / Edge 4096 bytes (~4KB) 180 超過限制後,會按LRU策略清理最舊的Cookie。
Firefox 4097 bytes 150 略大於4KB。
Safari 4096 bytes (~4KB) 無明確硬性限制 但實際也有約束,行為可能不同。
IE6 以及更低版本 20

注意

  • 總大小限制:一些瀏覽器還對每個域名下所有 Cookie 的總大小有限制(例如約1024KB),但這不是一個普遍嚴格的標準。
  • 超出限制的後果:當設置新 Cookie 導致超出限制時,瀏覽器會靜默地刪除一個或多箇舊的Cookie(通常是LRU),而不是報錯。

最佳實踐:永遠不要接近這些限制。

常見問題

如何唯一標識一個Cookie?

簡短回答:瀏覽器會根據 Cookie 的 Name、Domain 和 Path 這三個屬性的組合來唯一標識一個Cookie。

也就是説,只要 NameDomainPath 這三個屬性中有任何一個不同,瀏覽器就會將其視為不同的、獨立的Cookie,即使它們的 Value 完全相同。

為什麼 Max-Age 更好?

expires 是 http/1.0 協議的,其值是一個GMT 格式的時間;而 max-age 是 http/1.1 協議的,用於代替 expires,其值是一個以為單位時間段。

注意: 同時設置 expires 和 max-age,Max-Age 的優先級高於 Expires

Max-Age 之所以被設計為優先級更高,主要是因為它有以下幾個優勢:

  • 更簡單直觀:使用秒數來設置,無需生成複雜的 GMT 時間字符串。
  • 更可靠:Max-Age 是相對時間,基於客户端收到 Cookie 的時刻計算。而 Expires 是絕對時間,如果客户端(瀏覽器)的系統時鐘不準確(過快或過慢),就會導致 Cookie 過早或過晚過期。
  • 符合現代標準:作為更新的標準,它被推薦使用。

最佳實踐:在現代Web開發中,建議優先使用 Max-Age,並避免同時設置兩者。如果為了兼容非常古老的客户端而需要同時設置,請確保 Max-Age 的值是你期望生效的值。

domain 值必須以點(.)開始嗎?

簡短回答:不是必須的。但帶點和不帶點有重要區別。

  • 帶點(例如 .zhao.com):明確地指定該 Cookie 應該被髮送給 zhao.com 及其所有子域名(如 www.zhao.comapi.zhao.comshop.zhao.com 等)。此時,點(.)類似一種“通配”聲明,主動將 Cookie 的作用範圍擴展到整個域名樹。
  • 不帶點(例如 zhao.com):在現代瀏覽器中,隱式地具有與帶點形式相同的效果,即,Cookie 也會被髮送給 zhao.com 及其所有子域名;而在非常古老的瀏覽器規範(早期 RFC 2109 標準)中,不帶點的設置可能只精確匹配該域名本身,即對子域無效。但根據現代的 RFC 6265 標準,不帶點的設置會被瀏覽器自動解釋為帶點的形式

最佳實踐:為了代碼清晰和簡潔,推薦使用不帶點的形式Domain=mydomain.com)。這是最廣泛使用和認可的方式。

path 值結尾需要包含 “/” 嗎 ?

簡短回答: 結尾加不加 “/” 效果是完全一樣的。

RFC 6265 標準

  • path 值為空或者第一個字符為非 "/" 字符,默認為 "/"
  • Cookie 路徑匹配,從 path 值第一個字符開始到最右邊的 "/",但不包括它。

最佳實踐: 從代碼清晰性和可讀性的角度,推薦使用結尾的 “/”

跨域和跨站是什麼概念?

什麼是跨域?

同源或者同域是兩個URL的“協議 + 域名 + 端口” 三部分是否完全相同。只要有任何一部分不同,就是跨域。

這是一個同源策略 下的概念,同源策略是瀏覽器最核心的安全基石,它限制了不同源的文檔或腳本如何相互交互。

當前頁面URL 目標URL 是否跨域 原因
https://www.example.com/app https://www.example.com/api 協議、域名、端口均相同
https://www.example.com http://www.example.com 協議不同 (https vs http)
https://www.example.com https://api.example.com 域名不同 (www vs api)
https://www.example.com https://www.example.com:8080 端口不同 (443 vs 8080)

AJAX請求(Fetch/XMLHttpRequest)默認受同源策略限制,跨域請求會被瀏覽器攔截,除非客户端、服務端明確設置允許跨域。

什麼是跨站?

同站是指兩個 URL 的 eTLD+1 相同,不需要考慮協議和端口。其中,eTLD 表示有效頂級域名,而 eTLD+1 則表示頂級域名 + 二級域名

這是一個 Cookie 的 SameSite 屬性 下的概念,主要用於防禦CSRF攻擊。

當前站點 目標站點 是否跨站 原因
https://www.example.com https://api.example.com 相同的 eTLD+1 (example.com)
https://www.example.com https://example.github.io 不同的 eTLD+1 (example.com vs example.github.io)
https://a.github.io https://b.github.io 注意! 雖然都是 github.io,但 github.io 本身是公共後綴,所以 a.github.iob.github.io 是不同的站點!
Cookie 不受同源策略限制?

簡單來説:Cookie 的安全邊界是“站點”,而不是“源”。它關心的是 Cookie 屬於哪個站點,而不是哪個源。

也就是説,Cookie 只關注 URL 的 eTLD+1 ,不關心協議和端口。比如:zhao.com 和 example.com 是跨站,它們的 Cookie 不能共享;而 a.zhao.com 和 b.zhao.com 是跨域的,但還是同站,它們的 Cookie 是可以共享的。

注意:Cookie 是否允許攜帶,除了必需是同站,還取決於 SameSite 屬性以及瀏覽器本身的 Cookie 管理策略。

Cookie發送不成功

  • 瀏覽器禁用了 Cookie:Chrome 瀏覽器,在右上角“更多”圖標 -> 設置 -> 隱私設置和安全性 -> 網站設置 -> Cookie 和網站數據;IE 瀏覽器,工具 -> internet選項 -> 隱私 -> 高級
  • 同源策略限制了 Cookie 發送(查看解決跨域的方法。)。

    • 前端請求頭沒有 withCredentials: true,或者服務端響應頭沒有 access-control-allow-credentials: trueaccess-control-allow-origin 沒有包含當前發請求的頁面的域名。
    • Cookie 的 Samesite 屬性沒有設置正確(比如 sameSite: Lax,在現在 Chrome 瀏覽器不允許跨站攜帶,但是 FireFox 瀏覽器或早期的 Chrome 瀏覽器是允許的),Samesite: None 是表示允許跨站攜帶 Cookie。
  • 其他原因:

    • 使用 Mockjs,但沒有開啓允許跨域:Mock.XHR.prototype.withCredentials = true(血淚經歷,記憶深刻)

withCredentials 和 sameSite 屬性

withCredentials 和 sameSite 屬性都跟 Cookie 跨站有關:

  • withCredentials:針對 XHR;
  • samesite:可針對 XHR,也包括 a、img、iframe 等標籤請求相關的 Cookie。

withCredentials 是請求頭/響應頭相關的信息,是為解決前端跨域請求問題。

使用該屬性需要以下三點:

  • 服務器響應頭信息 Access-Control-Allow-Credentials: true
  • 服務端響應頭信息必須攜帶 Access-Control-Allow-Origin,指定允許跨域的域名。

    規範中提到,如果 XMLHttpRequest 請求設置了 withCredentials 屬性,那麼服務器不得設置 Access-Control-Allow-Origin的值為* ,否則瀏覽器將會拋出 The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' 錯誤。
  • 客户端需要開啓請求頭部信息 withCredentials: true,如果未開啓,請求頭部不會發送請求服務器設置在客户端的 Cookie。(如果省略 withCredentials 設置,有的瀏覽器還是會一起發送 Cookie。這時,可以顯式關閉 withCredentials)

sameSite 是 Cookie 屬性,可限制客户端請求中 Cookie 跨站情況,其默認值在跨站請求時不會被髮送,從而可以阻止跨站請求偽造攻擊(即CSRF)。

注意:即使開啓了 withCredentials,此時客户端 Cookie 還是遵守同站政策,跨站發請求,仍無法將請求響應頭中 Cookie 寫到客户端,也無法將客户端 Cookie 讀取到 HTTP 請求頭。跨站攜帶,還是需要依賴 sameSite 屬性。

參考鏈接

關於Cookie的一些思考和理解

session與cookie詳解

cookie詳解

深入解析Cookie技術

聊一聊 cookie

瀏覽器系列之 Cookie 和 SameSite 屬性

Add a new Comments

Some HTML is okay.