大家好,我是若川。持續組織了8個月源碼共讀活動,感興趣的可以點此加我微信 ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列
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: <cookie-name>=<cookie-value> | 服務端通過設置 set-cookie 控制 Cookie |
瀏覽器 document.cookie | document.cookie = "name=scar"; | 獲取并設置與當前文檔相關聯的 cookie,操作不靈活。 |
瀏覽器 Cookie Store API | cookieStore.set("name", "scar") | 新特性,僅支持在 HTTPS 使用,目前還在實驗階段。 |
詳細說明
服務端:Set-Cookie
服務端以 Node.js 為例,不同語言有不同的用法,但 Cookie 設置邏輯是一樣的
const?http?=?require("http");
http.createServer((req,?res)?=>?{if?(req.url?===?"/read")?{//?讀取?Cookieres.end(`Read?Cookie:?${req.headers.cookie?||?""}`);}?else?if?(req.url?===?"/write")?{//?設置?Cookieres.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")?{//?刪除?cookieres.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?=?"name=scar";
document.cookie?=?"language=javascript";
//?讀取?Cookie
console.log(document.cookie);
//name=scar;?language=javascript//?刪除?Cookie
document.cookie?=?"name=scar;expires=Thu,?01?Jan?1970?00:00:01?GMT";
客戶端: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 無法設置成功。
名稱 | 說明 |
---|---|
__Secure- | 必須同時設置 Secure 屬性 |
__Host- | 必須同時設置 Path=/ 和 Secure 屬性,且不能設置 Domain 屬性。 限制來自安全域的 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 的一些額外信息,例如什么時候失效
速查表
Cookie 屬性 | 說明 | 類型 | 默認值 | 示例 |
---|---|---|---|---|
Domain | 生效域名 | String | 當前訪問地址中的 host 部分 | scar.site |
Path | 生效路徑 | String | / | /docs |
Expires | 過期時間 | Date | 瀏覽器會話關閉時間 | Thu, 22 Jul 2021 00:53:13 GMT |
Max-Age | 有效期,單位秒 | Number | 1000 | |
Secure | 僅 HTTPS 可用 | 無值 ,出現即設置 | ||
HttpOnly | 設置了 HttpOnly 屬性的 cookie 不能使用 JavaScript 進行訪問 | 無值 ,出現即設置 | ||
SameSite | 允許服務器設定 Cookie 不隨著跨站請求一起發送 | string | Lax | 值可能性為: Lax Strict None |
SameParty | 允許特定條件跨域共享 Cookie | 無值 ,出現即設置 | ||
Priority | 優先級,僅 Chrome 支持 | Medium | 值可能性為: Low Medium High |
詳細說明
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
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
掃碼加我微信 ruochuan02、拉你進源碼共讀群
今日話題
目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 ruochuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~