你想知道的 HTTP Cookie 都在這
1 為什么有 Cookie
1.1 背景
客戶端和服務(wù)器通過(guò) HTTP 協(xié)議通信,它是一種無(wú)狀態(tài)的協(xié)議,客戶端每次發(fā)送請(qǐng)求時(shí),首先要和服務(wù)器端建立一個(gè)連接,在請(qǐng)求完成后又會(huì)斷開(kāi)這個(gè)連接。這種方式可以節(jié)省傳輸時(shí)占用的連接資源,但同時(shí)也存在一個(gè)問(wèn)題:每次請(qǐng)求都是獨(dú)立的,服務(wù)器端無(wú)法判斷本次請(qǐng)求和上一次請(qǐng)求是否來(lái)自同一個(gè)用戶。
為了解決 HTTP 無(wú)狀態(tài)的問(wèn)題,Lou Montulli 在 1994 年的時(shí)候,推出了 Cookie。
1.2 什么是 Cookie
HTTP Cookie(也叫 Web Cookie 或?yàn)g覽器 Cookie)是服務(wù)器發(fā)送到用戶瀏覽器并保存在本地的一小塊數(shù)據(jù),瀏覽器會(huì)存儲(chǔ) Cookie 并在下次發(fā)起請(qǐng)求時(shí)攜帶。如下圖示例:

注:根據(jù) HTTP 協(xié)議的規(guī)定,每個(gè)域名下的 Cookie 總大小不能超過(guò)4KB(4096字節(jié))。如果超過(guò)該限制,瀏覽器會(huì)自動(dòng)截?cái)?Cookie 內(nèi)容。
2 如何種 Cookie
2.1 后端

服務(wù)端示例代碼(node)
const https = require('https');
const path = require('path');
const fs = require('fs');
let cookieNum = 0;
const server = https.createServer(options, (req, res) => {
if (req.headers.origin) {
// 設(shè)置允許跨域的域名
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
// 設(shè)置允許的請(qǐng)求方法
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
// 設(shè)置允許的請(qǐng)求頭
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Allow-Credentials', true);
}
// 處理預(yù)檢請(qǐng)求(OPTIONS請(qǐng)求)
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// 處理其他請(qǐng)求
if (req.url === '/login' && req.method === 'GET') {
// 設(shè)置一個(gè)名為 "my-cookie" 的 Cookie,值為 "abc-*",有效期為 1 小時(shí)
res.setHeader(
'Set-Cookie',
`my-cookie=abc-${cookieNum++}; Max-Age=3600; HttpOnly; Domain=b.com; SameSite=None; Secure;`
);
res.end('Cookie has been set!');
} else if (req.url === '/getList' && req.method === 'GET') {
res.end();
} else {
res.writeHead(404);
res.end();
}
});
server.listen(3001, () => {
console.log('Server is running on port 3001');
});
可在瀏覽器查看種完的 Cookie,如下:

注:只能種跟接口域名同站的 Cookie,比如 test.b.com/login 接口不能種 Domain=c.com 下的 Cookie。

2.2 Cookie 屬性
2.2.1 Expires & Max-Age
兩者都可以設(shè)置 Cookie 有效期,Max-Age 優(yōu)先級(jí)更高。過(guò)期后 Cookie 就不會(huì)再跟著請(qǐng)求發(fā)送到服務(wù)器。也會(huì)自動(dòng)刪除 Chrome DevTools -> Application -> Cookies 過(guò)期的 Cookie。
2.2.2 Domain
控制 Cookie 在哪些域名下可見(jiàn)可訪問(wèn),如果指定了一個(gè) Cookie Domain=ele.me,則它與所有的子域名(help.ele.me)共享 Cookie。可以理解為 Cookie 遵守的是「同站」策略。
什么是同站:只要兩個(gè) URL 的 eTLD+1 相同即是「同站」,否則就是「跨站」,不需要考慮協(xié)議和端口。定義如下:
eTLD: (effective top-level domain) 有效頂級(jí)域名,如 .com、.me、.github.io、.top 等
eTLD+1: 有效頂級(jí)域名 + 二級(jí)域名,如 ele.me,taobao.com
2.2.3 Path
控制 Cookie 在哪些路徑下可訪問(wèn),默認(rèn)值為 / 也就是所有接口路徑都可訪問(wèn)
如:Path=/api/
https://ele.me/api/getList 會(huì)攜帶 Cookie
https://ele.me/abc/getList 不攜帶 Cookie
2.2.4 HttpOnly
阻止 JavaScript 通過(guò) Document.cookie 屬性讀寫 Cookie。通過(guò)document.cookieapi 無(wú)法讀、寫對(duì)應(yīng)的 Cookie
2.2.5 Secure
只有 https 協(xié)議(localhost 不受此限制)的請(qǐng)求才會(huì)攜帶 Cookie
2.2.6 SameSite
限制跨站請(qǐng)求不發(fā)送 Cookie,這樣可以在一定程度上防范跨站請(qǐng)求偽造攻擊(CSRF)
Strict(嚴(yán)格): 只發(fā)送同站請(qǐng)求的 Cookie,不會(huì)發(fā)送任何形式的跨站 Cookie
Lax(寬松): 默認(rèn)值,一般情況下只發(fā)送同站 Cookie,有一些特殊情況可以發(fā)送跨站 Cookie,需要滿足以下條件可以發(fā)送跨站 Cookie:
- 必須是 GET 或者 HEAD 請(qǐng)求,不能是 POST 請(qǐng)求
- 必須是頂級(jí)導(dǎo)航請(qǐng)求,可以認(rèn)為該請(qǐng)求會(huì)改變?yōu)g覽器地址欄 URL,比如通過(guò) a 標(biāo)簽跳轉(zhuǎn)的請(qǐng)求
舉例:先在 test.b.com 網(wǎng)站下用 test.b.com/login 接口種 Lax Cookie。然后打開(kāi) test.a.com 頁(yè)面,如下圖,該頁(yè)面有個(gè) a 標(biāo)簽,點(diǎn)擊會(huì)跳轉(zhuǎn)到 test.b.com,此時(shí)會(huì)攜帶 Lax Cookie,但不會(huì)攜帶 Strict Cookie

None: 跨站和同站請(qǐng)求均發(fā)送 Cookie。在設(shè)置這個(gè)屬性值時(shí),必須同時(shí)設(shè)置 Secure 屬性,比如:SameSite=None; Secure


SameSite=None + Secure 屬性的方式在瀏覽器禁用三方 Cookie 之前可用,如果瀏覽器禁用三方 Cookie 需要怎么設(shè)置呢?-- 需要配合 Partitioned 屬性一起使用
2.2.7 Partitioned
在瀏覽器禁用三方 Cookie 之前SameSite=None; Secure方式可正常使用,禁用三方 Cookie 后需要配合 Partitioned 屬性使用。瀏覽器開(kāi)啟禁用三方 Cookie 時(shí),set cookie 報(bào)錯(cuò),如下:

當(dāng) Set Cookie 增加 Partitioned 屬性后,Cookie 種成功,如下:

對(duì)應(yīng)域的請(qǐng)求也可以正常攜帶 Cookie,如下:

設(shè)置 Partitioned 屬性之前
到這里你可能會(huì)疑問(wèn),為什么要增加 Partitioned 屬性?不是多此一舉嗎?那我們看下之前有什么問(wèn)題
隱私問(wèn)題
三方 Cookie 可以跨站跟蹤用戶的行為,因此可能會(huì)泄露用戶的個(gè)人信息和瀏覽習(xí)慣,比如:用戶先訪問(wèn)淘寶,網(wǎng)站會(huì)通過(guò) gm.mmstat.com/arms.1.1 接口種一個(gè) sca Cookie,然后用戶點(diǎn)擊感興趣的商品也會(huì)有 gm.mmstat.com 埋點(diǎn)請(qǐng)求,同時(shí)會(huì)攜帶 sca Cookie,如下圖:

之后用戶打開(kāi)天貓,也會(huì)攜帶淘寶網(wǎng)站生成的 sca Cookie,這樣 gm.mmstat.com 對(duì)應(yīng)的服務(wù)就有能力分析出來(lái)用戶的行為路徑,用戶的行為習(xí)慣就會(huì)被追蹤

安全問(wèn)題
三方 Cookie 可能被黑客利用來(lái)進(jìn)行惡意攻擊,比如偽造跨站請(qǐng)求攻擊(CSRF),示例如下:

加 Partitioned 屬性之后
加 Partitioned 屬性后,Chrome 內(nèi)核(從 114 版本開(kāi)始)的瀏覽器會(huì)對(duì) Cookie 進(jìn)行分區(qū)。如下圖,站點(diǎn) A 內(nèi)嵌入了站點(diǎn) C,站點(diǎn) C 設(shè)置了一個(gè)帶有 Partitioned 屬性的 Cookie,這個(gè) Cookie 將保存在一個(gè)專門用于站點(diǎn) A 嵌入 C 時(shí)設(shè)置的 Cookie 分區(qū)中。只有 C 是 A 網(wǎng)站的子應(yīng)用時(shí)才會(huì)發(fā)送該 Cookie。
這時(shí)有個(gè)新站點(diǎn) B,內(nèi)嵌了站點(diǎn)C,這時(shí) C 站點(diǎn)接口不會(huì)攜帶上面在 A 站點(diǎn)設(shè)置的 Cookie

如果用戶直接訪問(wèn) C 網(wǎng)站,C 網(wǎng)站的請(qǐng)求不會(huì)攜帶嵌入在 A 中設(shè)置的分區(qū) Cookie,這樣既能保證當(dāng)前頁(yè)面三方 Cookie 的可用性,也能避免上面提到的隱私問(wèn)題、安全問(wèn)題。

2.3 前端
前端種 Cookie 代碼如下:
document.cookie = 'my-js-cookie=abc; Max-Age=3600; Path=/; Domain=b.com; SameSite=None; Secure;'

局限性
-
只能種跟當(dāng)前頁(yè)面域名同站的 Cookie 比如,在 test.a.com 頁(yè)面下,document.cookieapi 只能種 Domain=a.com 下的 Cookie,不能種 Domain=b.com 下的 Cookie。
-
不能重寫有 HttpOnly 屬性的同名 Cookie 線上故障 Case:某App iOS 端 默認(rèn)向 webview 容器里種 sid Cookie 作為登錄態(tài),并且指定 HttpOnly 屬性,但切換賬號(hào)后容器并沒(méi)有更新 sid,H5 使用 document.cookie api 也無(wú)法重寫 sid,導(dǎo)致內(nèi)嵌的 H5 頁(yè)面鑒權(quán)失敗。如果使用后端接口種 Cookie 就沒(méi)有這個(gè)問(wèn)題。
-
安全問(wèn)題,跨站請(qǐng)求偽造攻擊(CSRF)
3 瀏覽器禁用三方 Cookie 計(jì)劃
3.1 Chrome 禁用三方 Cookie
從 Chrome 114 版本開(kāi)始,無(wú)痕模式默認(rèn)開(kāi)啟禁用三方 Cookie。


下面是 Chrome 禁用三方 Cookie 的計(jì)劃

3.2 Safari 禁用三方 Cookie
目前(2023.11.8) Safari 瀏覽器默認(rèn)禁用三方 Cookie,也不支持 Partitioned 屬性,需要手動(dòng)設(shè)置允許跨站 Cookie 才行,設(shè)置入口如下:

3.3 對(duì)現(xiàn)有業(yè)務(wù)影響
只要使用了三方 Cookie 并且沒(méi)有 Partitioned 屬性的業(yè)務(wù)都會(huì)受到影響。
4 三方 Cookie
Cookie Domain 與 頁(yè)面 URL eTLD+1 不同
eTLD+1: 有效頂級(jí)域名 + 二級(jí)域名,如 ele.me,taobao.com,如:test.a.com 頁(yè)面下種了 Domain=b.com Cookie,則是三方 Cookie
請(qǐng)求接口協(xié)議與頁(yè)面協(xié)議不同也屬于三方 Cookie
如果頁(yè)面域名和接口域名的 eTLD+1 相同,但頁(yè)面域名是 http 協(xié)議,接口是 https 協(xié)議,也屬于三方 Cookie
iframe 嵌入的三方子應(yīng)用
如果嵌入的 iframe 子應(yīng)用域名與宿主應(yīng)用域名不是同站,則它是第三方內(nèi)容,三方網(wǎng)站內(nèi)使用的 Cookie 都屬于三方 Cookie。
如下圖,宿主頁(yè)面(test.a.com)通過(guò) iframe 嵌入子頁(yè)面(test.b.com),子頁(yè)面無(wú)法設(shè)置 Domain=b.com 域下的未分區(qū)的 Cookie。
5 參考
- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Overview#http_%E6%98%AF%E6%97%A0%E7%8A%B6%E6%80%81%EF%BC%8C%E6%9C%89%E4%BC%9A%E8%AF%9D%E7%9A%84
- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies
- https://juejin.cn/post/6926731819903631368?searchId=20231106120717EBA1AB4DB9821BC59D77
- https://developer.chrome.com/docs/privacy-sandbox/chips/
- https://developer.chrome.com/blog/cookie-countdown-2023oct/
- https://developer.chrome.com/docs/privacy-sandbox/third-party-cookie-phase-out/
- https://web.dev/articles/samesite-cookie-recipes?hl=zh-cn#use_cases_for_cross_site_or_third_party_cookies

