Cookie 從入門到進階:一文徹底弄懂其原理以及應用
來自秦一授權的分享
Cookie 是什麼?
Fortune cookie
Cookie,它的名字源自一種叫 Fortune cookie 的餅乾,這種餅乾裏面有一張寫着精闢句子的小紙條。
在瀏覽器中,Cookie 是服務器讓瀏覽器幫忙攜帶信息的手段,就像餅乾裏的紙條,瀏覽器會儲存它,並且在後續的 HTTP 請求中再次發送給服務器。
Cookie 應用
主要用於以下三個方面:
-
會話狀態管理(如用戶登錄狀態、購物車、遊戲分數或其它需要記錄的信息)
-
個性化設置(如用戶自定義設置、主題等)
-
瀏覽器行爲跟蹤(如跟蹤分析用戶行爲等)
因爲 HTTP 是無狀態的,所以爲了協助 Web 保持狀態,Cookie 誕生了。在 HTML5 的 localStorage、sessionStorage 出現之前,它作爲當時唯一的儲存手段也曾一度被用於客戶端儲存。
隨着瀏覽器儲存機制的完善,爲了減小不必要的性能開銷(因爲每次請求瀏覽器都會攜帶 Cookie 數據),一些客戶端需要而服務器不需要的數據的場景漸漸被其他儲存方式替代,例如記住用戶的主題信息,Cookie 的應用場景也漸漸迴歸初心。
目前 Cookie 主要用於會話狀態管理,以用戶登錄 - 退出登陸爲例,Cookie 的生命週期如下:
前端通過用戶登錄 API 向後端傳遞用戶信息,後端覈對與數據庫信息是否匹配。
匹配後在登錄 API 返回頭部 set-cookie
返回記錄用戶狀態的 cookie 值 userToken:
瀏覽器按照 set-cookie
的規則解析後存入瀏覽器
後續瀏覽器會自動將 userToken 加到滿足條件(域名、路徑)的 API 的 請求頭部 cookie 中
如果退出登陸,返回頭部的 set-cookie
會拜託瀏覽器幫忙刪除 userToken,瀏覽器的 cookie 儲存庫就會將 userToken 字段刪除,後續的 API 請求頭部 cookie
也不會發送它
如何設置 Cookie
服務端和瀏覽器有不同設置 Cookie 的方式。
速查表
詳細說明
服務端:Set-Cookie
服務端以 Node.js 爲例,不同語言有不同的用法,但 Cookie 設置邏輯是一樣的
const http = require("http");
http
.createServer((req, res) => {
if (req.url === "/read") {
// 讀取 Cookie
res.end(`Read Cookie: ${req.headers.cookie || ""}`);
} else if (req.url === "/write") {
// 設置 Cookie
res.setHeader("Set-Cookie", [
`name=scar;`,
//set-cookie 屬性大小寫不敏感,你可以寫成 path=/ 或者 Path=/
`language=javascript;Path=/; HttpOnly;Expires=${new Date(
Date.now() + 1000
).toUTCString()};`,
]);
res.end("Write Success");
} else if (req.url === "/delete") {
// 刪除 cookie
res.setHeader("Set-Cookie", [
// 設置過期時間爲過去的時間
`name=;expires=${new Date(1).toUTCString()}`,
// 有效期 max-age 設置成 0 或 -1 這種無效秒,讓 cookie 當場去世
// 有些瀏覽器不支持 max-age 屬性,所以用此方法需要考慮兼容性
"language=javascript; max-age=0",
]);
res.end("Delete Success");
} else {
res.end("Not Found");
}
})
.listen(3000);
客戶端:document.cookie
客戶端通過瀏覽器方法 document.cookie 讀寫當前界面的 Cookie。
// 編輯 ookie
document.cookie = ";
document.cookie = "language=javascript";
// 讀取 Cookie
console.log(document.cookie);
//name=scar; language=javascript
// 刪除 Cookie
document.cookie = ";
客戶端:Cookie Store API
Cookie Store API 目前正在試驗階段,Firefox、Safari 瀏覽器 還不支持,所以不建議在生產環境使用,相信在將來我們會用上它更方便地操作 Cookie。
// 讀取 Cookie
await cookieStore.get("enName");
await cookieStore.getAll();
// 設置 Cookie
const day = 24 * 60 * 60 * 1000;
cookieStore
.set({
name: "enName",
value: "scar",
expires: Date.now() + day,
domain: "scar.siteÏ",
})
.then(
function () {
console.log("It worked!");
},
function (reason) {
console.error("It failed: ", reason);
}
);
// 刪除 Cookie
await cookieStore.delete("session_id");
// 監聽 Cookie 變化
cookieStore.addEventListener("change", (event) => {
for (const cookie of event.changed) {
if (cookie.name === "name") sessionCookieChanged(cookie.value);
}
for (const cookie of event.deleted) {
if (cookie.name === "enName") sessionCookieChanged(null);
}
});
除了更方便的用法,他還有以下特性:
異步操作
它可以異步訪問 Cookie,不阻塞主進程,document.cookie 是同步操作。
錯誤拋出機制
Cookie Store API 有一個明確的機制來報告 Cookie 存儲錯誤,而 document.cookie 如果設置失敗也不會提醒,所以需要輪詢查 Cookie 的方法來確保設置成功。
service workers 支持
因爲 document.cookie 的同步設計,所以 service workers 不支持。Cookie Store API 的異步特性更適合,所以 service workers 支持通過它訪問 Cookie。
Set-Cookie 詳解
從語法可以看出,Set-Cookie 由前綴、鍵值對、屬性三部分組成。
Set-Cookie: <cookie-name>=<cookie-value>
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
// 同時指定多個屬性 Domain、Secure、HttpOnly
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly
// cookie 前綴,值可能性爲 __Secure-、__Host-
Set-Cookie: <cookie-prefix><cookie-name>=<cookie-value>
-
前綴 [非必須]
-
示例:
Set-cookie: __Secure-lol=foo;Domain=example.xxx
, _Sercure- 就是前綴 -
Cookie 前綴需配合屬性使用,使 Cookie 更安全
-
鍵值對(名稱 = 值)
-
示例:
Set-cookie: __Secure-lol=foo
。 -
Cookie 攜帶信息
-
屬性 [非必須]
-
示例:
Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2019 00:00:00 GMT
-
Cookie 的設置,告訴客戶端如何使用 Cookie 信息,可以設置 Cookie 生效的時間、域名等等信息。
Cookie 前綴
Cookie 前綴是一種在 Cookie 名稱中攜帶信息的方式,它必須和某些屬性同時出現,否則 Cookie 無法設置成功。
例子:
// 支持 Cookie 前綴的收到下面設置的時候會拒絕,因爲沒有同時設置 Secure 屬性
document.cookie = "__Secure-invalid-without-secure=1";
// 這樣設置才能成功!
document.cookie = "__Secure-valid-with-secure=1; Secure";
// 當響應來自於一個安全域(HTTPS)的時候,二者都可以被客戶端接受
Set-Cookie: __Secure-ID=123; Secure; Domain=example.com
Set-Cookie: __Host-ID=123; Secure; Path=/
// 缺少 Secure 屬性,會被拒絕
Set-Cookie: __Secure-id=1
// 缺少 Path=/ ,會被拒絕
Set-Cookie: __Host-id=1; Secure
說實話這個前綴我之前從沒見過,孤陋寡聞了。
除了 IE 不支持,其他各大瀏覽器基本支持。不同瀏覽器限制可能不同,例如:Set-Cookie: __Host-id=1; Secure
設置 __Host-
前綴的時候,即使缺少 Path=/
,FireFox 可以設置成功,而 Chrome 會拒絕。
但爲什麼有
Secure
屬性還要加個__Secure-
前綴呢?因爲
Secure
屬性設置後是可以被人惡意移除的,而 Cookie 名稱被人移除前綴,服務器不會認它,所以更加安全。
Cookie 鍵值對
<cookie-name>=<cookie-value>
真正攜帶信息的部分,例如:
id=38afes7a8
<cookie-name>
可以是除了控制字符 (CTLs)、空格 (spaces) 或製表符 (tab) 之外的任何 US-ASCII 字符。同時不能包含以下分隔字符:( ) < > @ , ; : \ " / [ ] ? = { }。
<cookie-value>
非必填,如果有值,那麼需要包含在雙引號裏面。支持除了控制字符(CTLs)、空格(whitespace)、雙引號(double quotes)、逗號(comma)、分號(semicolon)以及反斜線(backslash)之外的任意 US-ASCII 字符。
Cookie 屬性
Cookie 屬性可以理解爲 Cookie 的配置項,告訴瀏覽器 Cookie 的一些額外信息,例如什麼時候失效
速查表
詳細說明
Domain
Domain
指定了哪些主機地址可以接收 Cookie。來看看如下設置:
Set-Cookie: __Secure-ID=123; Secure; Domain=example.com
表示只要請求的目標地址匹配 Domain 規則,那 Cookie 就會被髮送過去,所即使scar.site
向 example.com
發起的請求,Cookie 會被髮送過去。所以不要再誤會 Domain
是發起請求的域名啦,其實是接受請求的域名。
如果不設置,默認爲 origin,不包含子域名,如果指定了Domain
,則一般包含子域名,例如,如果設置 Domain=mozilla.org
,則 Cookie 也包含在子域名中(如developer.mozilla.org
)。
當前大多數瀏覽器遵循 RFC 6265,設置
Domain
時 不需要加前導點。瀏覽器不遵循該規範,則需要加前導點,例如:Domain=.mozilla.org
Path
Path
標識指定了主機下的哪些路徑可以接受 Cookie(該 URL 路徑必須存在於請求 URL 中)。以字符 %x2F
("/") 作爲路徑分隔符,子路徑也會被匹配。
例如,設置 Path=/docs
,則以下地址都會匹配:
-
/docs
-
/docs/Web/
-
/docs/Web/HTTP
Expires
設置過期時間,可以傳一個符合 HTTP-Date 格式的值,例如:expires=Mon, 14 Mar 2022 15:39:34 GMT;
如果不設置 Expires
,則默認爲會話關閉時間;
會話是瀏覽器的一個概念,一般是一個瀏覽器 Tab 窗口。
如果瀏覽器提供了會話恢復功能,恢復回話之時,Cookie 也會一起恢復,就好像會話從來沒有關閉一樣。
Max-Age
在 Cookie 失效之前需要經過的秒數。秒數爲 0 或 -1 將會使 cookie 直接過期。一些老的瀏覽器(IE 6、IE 7 和 IE 8)不支持這個屬性。
如果 Expires
和Max-Age
同時存在時,Max-Age
優先級更高。
HttpOnly
設置了 HttpOnly 屬性的 cookie 不能使用 JavaScript 經由 Document.cookie
屬性、XMLHttpRequest
和 Request
APIs、Cookie Store
APIs 進行訪問。
Secure
標記爲 Secure
的 Cookie 只應通過被 HTTPS 協議加密過的請求發送給服務端,因此可以預防 man-in-the-middle 攻擊。
但即便設置了 Secure
標記,敏感信息也不應該通過 Cookie 傳輸,因爲 Cookie 固有的不安全性,Secure
標記也無法提供確實的安全保障,例如:可以訪問客戶端硬盤的人可以讀取它。
SameSite
服務器要求某個 Cookie 在跨站請求時不會被髮送,從而可以阻止跨站請求僞造攻擊。
SameSite 可以有下面三種值:
-
None
:瀏覽器會在同站請求、跨站請求下繼續發送 Cookies,不區分大小寫。 -
Strict
:瀏覽器將只在訪問站點與 Cookie 生效的域相同時發送 Cookie。 -
Lax
:新版本瀏覽器默認設置爲 Lax。與Strict
類似,但是特定條件也可以發送跨域請求:舉個例子,用戶從 a.com 跳轉到 b.com,URL 發生了變化的同時請求方式爲 GET,所以 設置了
Lax
的 Cookie 會被髮送過去 ,除此之外還有圖片加載、iframe 調用等等。 -
URL 必須發生變化
-
HTTP 請求方式必須是安全的,例如
GET
、HEAD
、OPTIONS
,它們不改變數據。
SameSite 和 Domain 的區別
上面提到過,Domain
可以指定 Cookie 生效的域名,那它和 Domain
的區別是什麼呢?
Domain
屬性限制了接收 Cookie 的域名,而 SameSite
屬性限制了發送 Cookie 的域名
舉個例子:
Set-Cookie: Foo=bar; Path=/; Secure; Domain=scar.site;
無論是從 scar.site
還是 foo.example.com
發起的請求,只要被髮送到了 scar.site
或者它的子域名,那 Cookie 就能發過去。
Set-Cookie: name=scar; Path=/; Secure; Domain=scar.site;SameSite=strict;
如果設置了 SameSite=strict
, 那麼這個請求只能從 scar.site
發起 Cookie 才能帶過去。
SameParty
目前還在實驗階段
配合 First-Party Sets
實現跨域共享屬性,詳細可以訪問:詳解 Cookie 新增的 SameParty 屬性。
Priority
目前只有 Chrome 實現了這個提案
因爲 Cookie 有數量限制,所以在 Cookie 超過一定數量時,瀏覽器會清除最早過期的 Cookie。
如果設置了 Priority,Chrome 會先將優先級低的清除,並且每種優先級 Cookie 至少保留一個。可以有下面三種值:
-
Low:低優先級
-
Medium:默認值,中優先級
-
High:高優先級
Q&A
一些關於 Cookie 的疑問和新特性,以 Q&A 形式記錄。比較雜、比較散,可以說沒什麼知識點全是感情,屬於那種你知道了可能沒什麼用但是就是想把它弄懂。
1. Cookie 的限制
大小限制
大多數瀏覽器支持最大爲 4KB 的 Cookie,4KB 是針對 Cookie 單條記錄的 Value 值。
數量限制
Cookie 有數量限制,而且只允許每個站點存儲一定數量的 Cookie,當超過時,最早過期的 Cookie 便被刪除。
不同瀏覽器支持的數量可能不同, 基於 Webkit 內核的是 180 個,基於 gecko 內核的是 150 個,感興趣可以訪問江濤學編程 - 編寫的 Cookie 實驗 試試自己的瀏覽器 Cookie 數量限制
實際上影響 Cookie 被刪除的要素不止是
Expires
和Max-Age
,還有Priority
、Secure
,對移除策略感興趣的可以看:Cookie 知識二則
2. 和 Cookie 相關的不安全事件有哪些?
CSRF 攻擊
CSRF:跨站請求攻擊,簡單地說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經認證過的網站並運行一些操作(如發郵件,發消息,甚至財產操作如轉賬和購買商品)。
舉個例子,一家銀行用以運行轉賬操作的 URL 如下:
http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
那麼,一個惡意攻擊者可以在另一個網站上放置如下代碼:
<img src="<http://www.examplebank.com/withdraw?account=scar&amount=1000&for=Badman>">
如果有賬戶名爲 Alice 的用戶訪問了惡意站點,而她之前剛訪問過銀行不久,登錄信息尚未過期,導致發起請求後後端以爲是用戶正常操作,於是進行扣款操作,那麼她就會損失 1000 資金。通過設置 sameSite
可以防止跨域發送 Cookie,抵禦 CSRF。
XSS 攻擊
跨站腳本(Cross-site scripting)是一種網站應用程序的安全漏洞攻擊,簡稱爲 CSS, 但這會與層疊樣式表(Cascading Style Sheets)CSS 的縮寫混淆。因此,跨站腳本攻擊縮寫爲 XSS。
XSS 攻擊通常指的是通過利用網頁開發時留下的漏洞,通過巧妙的方法注入惡意指令代碼到網頁,使用戶加載並執行攻擊者惡意製造的網頁程序。攻擊成功後,攻擊者可能得到 Cookie 從而實現攻擊。
3. Floc 替代第三方 Cookie?
引用自:如果不用第三方 Cookie,Google FLoC 會是更好的替代者嗎?- 少數派
FLoC 是一種新的廣告追蹤技術,全稱爲 Federated Learning of Cohorts,即「同類羣組聯合學習」。FLoC 的工作原理是監視你的瀏覽記錄,爲訪客的彙總行爲分配一個 ID,然後將具有類似瀏覽行爲的瀏覽器分組在一起。這些羣組的數據稱爲同類羣組,然後用於向人們展示針對性更強的廣告。
FLoC 在自身設計層面,是比 Cookie 隱私性更好的,但是首先它依然是一個廣告追蹤技術,其次纔是一個相對保護隱私的廣告追蹤技術。
FLoC 由 Google 主導,所以三方團體擔憂:當所有的瀏覽器開始默認屏蔽第三方 Cookie,廣告商轉向使用 FLoC 以後,Google 將在廣告追蹤市場一家獨大。
4. 同名 Cookie 發送時,優先級如何判斷?
Cookie: a=2; a=1
首先來看看 Cookie
發送順序,RFC 6265 提案提到:
-
Path
屬性較長的應該在前面 -
如果
Path
路徑一樣,創建時間早的在前面
具體的瀏覽器表現我沒有去探究,但提案只是倡導,所以每個客戶端不一定會按照它實現 Cookie 的發送順序。
除了考慮發送順序,還要考慮不同的服務器框架可能有不同的接收邏輯,所以筆者推薦儘量避免出現同名 Cookie,減少端表現不統一帶來的不確定性。
5. 如何快速調試 Cookie
F12 打開控制檯可以快速看到本域下的所有 Cookie
通過分析 Cookie 屬性來定位問題。
例如某個 Cookie 導致了業務問題,如果它設置了 HttpOnly,那麼代表客戶端無法操作 Cookie,可以快速的把問題定位到 API 層面。
總結
本來我在寫:Cookie、Session、Token ,寫着寫着發現 Cookie 的篇幅比較多,而那篇文章的重點不在於這些部分,所以摘了出來。如果對 Cookie、Session、Token 感興趣的可以持續關注一下我,近期會發哦~
作爲一名前端人員,平時用得少所以不熟悉,但瞭解後其實也沒有什麼難點,相信你們看完這篇就可以徹底瞭解了。如果文章中還有關於 Cookie 你想知道但是我沒寫的部分都可以評論,我會回覆。
參考資料
-
Cookies - mozilla
-
Set-Cookies - mozilla
-
Cookie 前綴如何讓 Cookie 更安全?- 阿里雲社區
-
What is a Session Cookie?
-
View, edit, and delete cookies
-
Correct way to delete cookies server-side
-
Feature: Cookie Store API
-
面試:徹底理解 Cookie 以及 Cookie 安全
-
如果不用第三方 Cookie,Google FLoC 會是更好的替代者嗎?- 少數派
-
FLoC 是什麼?以及你爲什麼需要在 Chrome 瀏覽器中禁用它 · Ruby China
-
分享一個關於 Cookie 做的實驗結果 - ataola - 博客園
-
What are the security differences between cookies with Domain vs SameSite strict? - Stack Overflow
-
What is difference between SameSite="Lax" and SameSite="Strict"? - Stack Overflow
-
Cookie 知識二則
-
How to handle multiple cookies with the same name? - Stack Overflow
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ljZ51xJZvG-8mx0CvU7vVA