使用 HTTP Cookie

一個**cookie**(也稱為網路 cookie 或瀏覽器 cookie)是伺服器傳送到使用者網路瀏覽器的一小段資料。瀏覽器可以儲存 cookie、建立新 cookie、修改現有 cookie 並將其與後續請求一起傳送回同一伺服器。Cookie 使 Web 應用程式能夠儲存有限數量的資料並記住狀態資訊;預設情況下,HTTP 協議是無狀態的

在本文中,我們將探討 cookie 的主要用途,解釋使用 cookie 的最佳實踐,並瞭解其隱私和安全影響。

Cookie 的用途

通常,伺服器將使用 HTTP cookie 的內容來確定不同的請求是否來自同一瀏覽器/使用者,然後發出個性化或通用響應。以下是使用者登入系統的一個非常簡單的描述

  1. 使用者將登入憑據傳送到伺服器,例如透過表單提交。
  2. 如果憑據正確,伺服器會更新 UI 以指示使用者已登入,並使用包含會話 ID 的 cookie 響應,該會話 ID 記錄其在瀏覽器上的登入狀態。
  3. 稍後,使用者移動到同一站點上的另一個頁面。瀏覽器將包含會話 ID 的 cookie 與相應的請求一起傳送,以指示它仍然認為使用者已登入。
  4. 伺服器檢查會話 ID,如果它仍然有效,則向用戶傳送新頁面的個性化版本。如果無效,則刪除會話 ID,並向用戶顯示頁面的通用版本(或者可能顯示“訪問被拒絕”訊息並要求使用者重新登入)。

visual representation of the above sign-in system description

Cookie 主要用於三個目的

  • 會話管理:使用者登入狀態、購物車內容、遊戲得分或伺服器需要記住的任何其他與使用者會話相關的資訊。
  • 個性化:使用者偏好,例如顯示語言和 UI 主題。
  • 跟蹤:記錄和分析使用者行為。

資料儲存

在 Web 早期,由於沒有其他選擇,Cookie 用於一般的客戶端資料儲存目的。現在建議使用現代儲存 API,例如Web 儲存 APIlocalStoragesessionStorage)和IndexedDB

它們專為儲存而設計,從不向伺服器傳送資料,並且沒有使用 cookie 進行儲存的其他缺點。

  • 瀏覽器通常限制每個域的最大 cookie 數量(因瀏覽器而異,通常為數百個),以及每個 cookie 的最大大小(通常為 4KB)。儲存 API 可以儲存更多資料。
  • Cookie 會隨每個請求一起傳送,因此它們會降低效能(例如在緩慢的移動資料連線上),尤其是在您設定了許多 cookie 的情況下。

注意:要檢視儲存的 cookie(以及網頁正在使用的其他儲存),您可以在 Firefox 開發者工具中使用儲存檢查器,或在 Chrome 開發者工具中使用應用程式面板

建立、刪除和更新 Cookie

在收到 HTTP 請求後,伺服器可以傳送一個或多個帶有響應的Set-Cookie標頭,每個標頭將設定一個單獨的 cookie。簡單的 cookie 透過指定名稱-值對來設定,如下所示

http
Set-Cookie: <cookie-name>=<cookie-value>

以下 HTTP 響應指示接收瀏覽器儲存一對 cookie

http
HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

[page content]

注意:瞭解如何在各種伺服器端語言/框架中使用Set-Cookie標頭:PHPNode.JSPythonRuby on Rails

當發出新請求時,瀏覽器通常會將先前儲存的當前域的 cookie 傳送回伺服器,位於CookieHTTP 標頭中

http
GET /sample_page.html HTTP/2.0
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

您可以指定一個過期日期或時間段,在此之後應刪除 cookie 並且不再發送。根據在建立 cookie 時Set-Cookie標頭中設定的屬性,它們可以是永久會話 cookie

  • 永久 cookie 在Expires屬性中指定的日期後刪除
    http
    Set-Cookie: id=a3fWa; Expires=Thu, 31 Oct 2021 07:28:00 GMT;
    
    或在Max-Age屬性中指定的時間段後
    http
    Set-Cookie: id=a3fWa; Max-Age=2592000
    

    注意:ExpiresMax-Age可用時間更長,但是Max-Age出錯的可能性更小,並且在兩者都設定時優先。這樣做的原因是,當您設定Expires日期和時間時,它們相對於設定 cookie 的客戶端。如果伺服器設定為不同的時間,這可能會導致錯誤。

  • 會話cookie——沒有Max-AgeExpires屬性的 cookie——在當前會話結束時刪除。瀏覽器定義“當前會話”何時結束,一些瀏覽器在重新啟動時使用會話恢復。這可能導致會話 cookie 持續無限期。

    注意:如果您的網站對使用者進行身份驗證,則應重新生成並重新發送會話 cookie,即使是已存在的 cookie,只要使用者進行身份驗證。此方法有助於防止會話固定攻擊,在這些攻擊中,第三方可以重用使用者的會話。

有一些技術旨在在 cookie 被刪除後重新建立它們。這些被稱為“殭屍”cookie。這些技術違反了使用者隱私和控制的原則,可能違反資料隱私法規,並且可能會使使用它們的網站承擔法律責任。

要透過 HTTP 更新 cookie,伺服器可以傳送一個帶有現有 cookie 的名稱和新值的Set-Cookie標頭。例如

http
Set-Cookie: id=new-value

您可能出於多種原因想要這樣做,例如,如果使用者更新了其偏好設定並且應用程式希望反映客戶端資料中的更改(您也可以使用客戶端儲存機制(例如Web 儲存)來做到這一點)。

透過 JavaScript 更新 cookie

在瀏覽器中,您可以使用Document.cookie屬性或非同步Cookie Store API透過 JavaScript 建立新的 cookie。請注意,以下所有示例都使用Document.cookie,因為它是支援最廣泛/最成熟的選項。

js
document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";

您還可以訪問現有 cookie 併為其設定新值,前提是HttpOnly屬性未在其上設定(即在建立它的Set-Cookie標頭中)

js
console.log(document.cookie);
// logs "yummy_cookie=choco; tasty_cookie=strawberry"

document.cookie = "yummy_cookie=blueberry";

console.log(document.cookie);
// logs "tasty_cookie=strawberry; yummy_cookie=blueberry"

請注意,出於安全目的,您不能透過在啟動請求時直接傳送更新的Cookie標頭來更改 cookie 值,即透過fetch()XMLHttpRequest。請注意,您不應允許 JavaScript 修改 cookie 也有充分的理由——即在建立期間設定HttpOnly。有關更多詳細資訊,請參閱安全部分。

安全

當您將資訊儲存在 cookie 中時,預設情況下,所有 cookie 值對終端使用者都是可見的,並且可以由終端使用者更改。您真的不希望您的 cookie 被濫用——例如被惡意攻擊者訪問/修改,或傳送到不應傳送到的域。潛在後果的範圍從令人煩惱——應用程式無法正常工作或表現出奇怪的行為——到災難性的。例如,犯罪分子可以竊取會話 ID 並使用它來設定一個 cookie,使其看起來像他們以其他人的身份登入,從而在此過程中控制其銀行或電子商務帳戶。

您可以透過多種方式保護您的 cookie,本節將對此進行審查。

阻止訪問您的 cookie

您可以透過兩種方式之一確保 cookie 以安全的方式傳送並且不會被意外的方或指令碼訪問:使用Secure屬性和HttpOnly屬性

http
Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly
  • 具有Secure屬性的 cookie 僅透過 HTTPS 協議上的加密請求傳送到伺服器。它永遠不會與不安全的 HTTP 一起傳送(本地主機除外),這意味著中間人攻擊者無法輕鬆訪問它。不安全的站點(URL 中帶有http:)無法設定具有Secure屬性的 cookie。但是,不要假設Secure可以防止所有對 cookie 中敏感資訊的訪問。例如,擁有客戶端硬碟訪問許可權(或 JavaScript 如果未設定HttpOnly屬性)的人可以讀取和修改資訊。
  • 具有HttpOnly屬性的 cookie 不能被 JavaScript 修改,例如使用Document.cookie;它只能在到達伺服器時修改。例如,保留使用者會話的 cookie 應設定HttpOnly屬性——讓它們對 JavaScript 可用將非常不安全。此預防措施有助於減輕跨站點指令碼(XSS)攻擊。

注意:根據應用程式,您可能希望使用伺服器查詢的不透明識別符號,而不是將敏感資訊直接儲存在 cookie 中,或者調查替代的身份驗證/機密性機制,例如JSON Web 令牌

定義 cookie 傳送到的位置

DomainPath屬性定義 cookie 的範圍:cookie 傳送到的 URL。

  • Domain 屬性指定哪個伺服器可以接收 Cookie。如果指定了,Cookie 可以在指定的伺服器及其子域上訪問。例如,如果您從 mozilla.org 設定了 Domain=mozilla.org,則 Cookie 可在該域及其子域(如 developer.mozilla.org)上使用。
    http
    Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly; Domain=mozilla.org
    
    如果 Set-Cookie 頭部沒有指定 Domain 屬性,則 Cookie 可在設定它的伺服器上使用,但不能在其子域上使用。因此,指定 Domain 比省略它限制性更小。請注意,伺服器只能將其 Domain 屬性設定為其自身域或父域,不能設定為子域或其他域。因此,例如,域為 foo.example.com 的伺服器可以將屬性設定為 example.comfoo.example.com,但不能設定為 bar.foo.example.comelsewhere.com(儘管 Cookie 仍會傳送到諸如 bar.foo.example.com 的子域)。有關更多詳細資訊,請參閱無效域
  • Path 屬性指示一個 URL 路徑,該路徑必須存在於請求的 URL 中才能傳送 Cookie 頭部。例如
    http
    Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly; Path=/docs
    
    %x2F("/")字元被視為目錄分隔符,子目錄也匹配。例如,如果您設定 Path=/docs,則以下請求路徑匹配
    • /docs
    • /docs/
    • /docs/Web/
    • /docs/Web/HTTP
    但以下請求路徑不匹配
    • /
    • /docsets
    • /en-US/docs

使用SameSite控制第三方 Cookie

SameSite 屬性允許伺服器指定是否/何時將 Cookie 與跨站點請求一起傳送 - 即 第三方 Cookie。跨站點請求是指站點(可註冊的域)和/或方案(http 或 https)與使用者當前訪問的站點不匹配的請求。這包括單擊其他站點上的連結以導航到您的站點的請求,以及嵌入的第三方內容傳送的任何請求。

SameSite 有助於防止資訊洩漏,維護使用者隱私,並提供一些針對跨站點請求偽造攻擊的保護。它採用三個可能的值:StrictLaxNone

  • Strict 使瀏覽器僅在響應來自 Cookie 原站點的請求時傳送 Cookie。當您擁有與始終位於初始導航之後的函式相關的 Cookie 時,例如身份驗證或儲存購物車資訊,應使用此選項。
    http
    Set-Cookie: cart=110045_77895_53420; SameSite=Strict
    

    注意:用於敏感資訊的 Cookie 也應具有較短的生命週期

  • Lax 類似,但當用戶導航到 Cookie 原站點時,瀏覽器也會發送 Cookie(即使使用者來自不同的站點)。這對於影響站點顯示的 Cookie 很有用 - 例如,您的網站上可能包含合作伙伴產品資訊以及聯盟連結。當用戶點選該連結訪問合作伙伴網站時,他們可能希望設定一個 Cookie 來表明已點選了聯盟連結,從而顯示獎勵橫幅並在購買產品時提供折扣。
    http
    Set-Cookie: affiliate=e4rt45dw; SameSite=Lax
    
  • None 指定在源請求和跨站點請求中都發送 Cookie。如果您希望將 Cookie 與從嵌入在其他站點中的第三方內容(例如廣告技術或分析提供商)發出的請求一起傳送,這將很有用。請注意,如果設定了 SameSite=None,則還必須設定 Secure 屬性 - SameSite=None 需要安全上下文
    http
    Set-Cookie: widget_session=7yjgj57e4n3d; SameSite=None; Secure; HttpOnly
    

如果沒有設定 SameSite 屬性,則 Cookie 預設情況下將被視為 Lax

由於 Cookie 機制的特性,伺服器無法確認 Cookie 是否是從安全來源設定的,甚至無法確定 Cookie 最初是在哪裡設定的。

子域上的易受攻擊的應用程式可以使用 Domain 屬性設定 Cookie,這使得可以訪問所有其他子域上的該 Cookie。這種機制可能被濫用於會話固定攻擊。有關主要緩解方法,請參閱會話固定

但是,作為縱深防禦措施,您可以使用 Cookie 字首來斷言有關 Cookie 的特定事實。有兩個字首可用

  • __Host-:如果 Cookie 名稱具有此字首,則僅當它也用 Secure 屬性標記、從安全來源傳送、包含 Domain 屬性且 Path 屬性設定為 / 時,才會在 Set-Cookie 標頭中接受它。換句話說,Cookie 是域鎖定的
  • __Secure-:如果 Cookie 名稱具有此字首,則僅當它用 Secure 屬性標記並從安全來源傳送時,才會在 Set-Cookie 標頭中接受它。這比 __Host- 字首弱。

瀏覽器將拒絕不符合其限制的具有這些字首的 Cookie。這確保了具有字首的子域建立的 Cookie 僅限於子域或完全被忽略。由於應用程式伺服器僅在確定使用者是否已透過身份驗證或 CSRF 令牌是否正確時檢查特定的 Cookie 名稱,因此這有效地充當了針對會話固定的防禦措施。

注意:在伺服器端,Web 應用程式必須檢查包含字首的完整 Cookie 名稱。使用者代理不會在請求的 Cookie 標頭中傳送 Cookie 之前從 Cookie 中刪除字首。

有關 Cookie 字首和瀏覽器支援的當前狀態的更多資訊,請參閱Set-Cookie 參考文章的字首部分

隱私和跟蹤

前面我們討論瞭如何使用 SameSite 屬性來控制何時傳送第三方 Cookie,以及這如何幫助維護使用者隱私。隱私在構建網站時是一個非常重要的考慮因素,如果做得好,可以建立使用者信任。如果做得不好,它可能會完全破壞這種信任並導致各種其他問題。

第三方 Cookie 可以透過 <iframe> 中的第三方內容嵌入到站點中。它們有許多合法用途,包括共享使用者個人資料資訊、統計廣告展示次數或跨不同相關域收集分析資料。

但是,第三方 Cookie 也可用於建立令人毛骨悚然、侵入性的使用者體驗。第三方伺服器可以根據同一瀏覽器在訪問多個站點時傳送給它的 Cookie 建立使用者瀏覽歷史記錄和習慣的配置檔案。一個經典的例子是,當您在一個網站上搜索產品資訊時,然後無論您走到哪裡,都會被類似產品的廣告追趕。

瀏覽器供應商知道使用者不喜歡這種行為,因此都開始預設阻止第三方 Cookie,或者至少制定了朝這個方向發展的計劃。其他瀏覽器設定或擴充套件程式也可能會阻止第三方 Cookie(或跟蹤 Cookie)。

注意:Cookie 阻止可能會導致某些第三方元件(如社交媒體小部件)無法按預期工作。隨著瀏覽器對第三方 Cookie 施加更多限制,開發人員應開始尋找減少對它們的依賴的方法。

請參閱我們的第三方 Cookie文章,瞭解有關第三方 Cookie、相關問題以及可用替代方案的詳細資訊。請參閱我們的隱私登入頁面,以獲取有關隱私的更多一般資訊。

涵蓋 Cookie 使用的法律法規包括

  • 歐盟的通用資料保護條例 (GDPR)
  • 歐盟的電子隱私指令
  • 加利福尼亞州消費者隱私法

這些法規具有全球影響力。它們適用於來自這些司法管轄區(歐盟和加利福尼亞州)的使用者訪問的全球資訊網上的任何站點(需要注意的是,加利福尼亞州的法律僅適用於總收入超過 2500 萬美元的實體等)。

這些法規包括以下要求

  • 通知使用者您的站點使用 Cookie。
  • 允許使用者選擇退出接收部分或全部 Cookie。
  • 允許使用者在不接收 Cookie 的情況下使用您服務的大部分內容。

您所在地區可能還有其他管理 Cookie 使用的法規。瞭解並遵守這些法規是您的責任。有一些公司提供“Cookie 橫幅”程式碼,可以幫助您遵守這些法規。

注意:出於透明目的併為了遵守法規,公司應披露其網站上使用的 Cookie 型別。例如,請參閱Google 關於其使用 Cookie 型別的通知和 Mozilla 的網站、通訊和 Cookie 隱私宣告

另請參閱