完整閱讀本文大約需要二十分鐘時間,可根據文章結構圖直接閱讀自己需要的部分。
1. Cookie 產生的背景
所有新技術的出現都是爲了解決某一痛點。 ——《前端三昧》
我們都知道,HTTP 協議
是==無狀態的==,服務器無法知道兩個請求是否來自同一個瀏覽器,也不知道用戶上一次做了什麼,每次請求都是完全相互獨立,這嚴重阻礙了交互式 Web
應用程序的實現。例子:
- 購物車:在典型的網上購物場景中,用戶瀏覽了幾個頁面,買了一盒餅乾和兩瓶飲料。最後結帳時,由於
HTTP
的無狀態性,不通過額外的手段,服務器並不知道用戶到底買了什麼。 - 登錄狀態:我們常用的“記住密碼”功能,在以前如果不是用
Cookie
記住了登錄憑據,想要實現該功能將會很複雜。
正是爲了解決這些交互方面存在的痛點,Cookie
應運而生。
2. Cookie 概述
Cookie
( 也叫Web Cookie
或瀏覽器 Cookie
)是服務器發送到用戶瀏覽器並保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶併發送到服務器上。
存儲 Cookie
是瀏覽器提供的功能。Cookie
其實是存儲在瀏覽器中的純文本,瀏覽器的安裝目錄下會專門有一個 Cookie
文件夾來存放各個域下設置的 Cookie
(非內存 Cookie
)。
通常,它用於==告知服務端兩個請求是否來自同一瀏覽器==,或者用來==保存一些狀態信息==,Cookie
使基於無狀態的 HTTP
協議記錄穩定的狀態信息成爲了可能。常用的有以下方面:
- 對話(
session
)管理:保存登錄、購物車等需要記錄的信息。 - 簡單的緩存:存儲一些簡單的業務數據,比如購物車等需要記錄的信息。
- 個性化:保存用戶的偏好,比如網頁的字體大小、背景色等等。
- 追蹤:記錄和分析用戶行爲。
Cookie
主要是用來存儲狀態的。
Cookie
曾一度用於客戶端數據的存儲,因當時並沒有其它合適的存儲辦法而作爲唯一的存儲手段。現在來說,這樣做雖然可行,但是並不推薦,因爲 Cookie
的設計目標並不是這個,它:
- 容量很小( 4KB )
- 缺乏數據操作接口
- 影響性能
客戶端儲存應該更多的考慮使用 localStorage
、sesseionStorage
和 IndexedDB
。
查看瀏覽器上存儲的 Cookie
的方法如下圖:
當然,瀏覽器可以設置不接受 Cookie
,也可以設置不向服務器發送 Cookie
。window.navigator.cookieEnabled
屬性返回一個布爾值,表示瀏覽器是否打開 Cookie
功能。
// 瀏覽器是否打開 Cookie 功能
window.navigator.cookieEnabled // true
本文所有的討論都是在瀏覽器的
window.navigator.cookieEnabled
爲 ==true== 的前提下進行的。
3. Cookie
的工作流程
4. Cookie 的限制
4.1 格式限制
Cookie
只能存儲純文本格式,因爲:
- 每條
Cookie
的大小有限制 - 爲用戶信息安全考慮,
Cookie
中存儲的是不可執行語句
4.2 大小和條數限制
由於 Cookie
是保存在客戶端上的,所以瀏覽器加入了一些限制確保 Cookie
不會被惡意使用,同時不會佔據太多磁盤空間,所以 Cookie
的數量和大小是有限的。
不同瀏覽器對 Cookie
數量和大小的限制,是不一樣的。一般來說,單個域設置的 Cookie
不應超過 ==50個==,每個 Cookie 的大小不能超過 ==4KB== 。超過限制以後,Cookie
將被==忽略==,不會被設置。
其限制的原因,主要在於阻止
Cookie
的濫用,而且Cookie
會被髮送到服務器端,如果數量太大的話,會嚴重影響請求的性能。以上這兩個限制條件,就是Cookie
爲什麼會被瀏覽器自動刪除的原因了。
4.3 域限制
不可跨域讀取,Cookie
是被哪個域寫入的,就只能被這個域及其子域讀取。比如:
由 test.com
寫入的 Cookie
可以被 test.com
和 test.com/child
讀取,而不能被 example.com
讀取。
4.4 路徑限制
存儲 Cookie
時會指定路徑,該路徑的子級可以讀取該 Cookie
,但是它的父級卻讀取不到——==子可以讀取父,但父不能拿到子==,例如:
由 test.com/parent/child
存儲下的 Cookie
,可以被 test.com/parent/child/child
讀取,但不能被 test.com/parent
讀取。
一般會將
Cookie
存在根路徑下,可以避免這種情況的發生。
4.5 時效限制
每個 Cookie
都有時效性,默認的有效期是==會話級別==( Seesion Cookie
):就是當瀏覽器關閉,那麼 Cookie
立即銷燬,但是我們也可以在存儲的時候手動設置 Cookie
的過期時間,具體設置方法會在下文講到。
5. Cookie 的屬性
5.1 Name/Value
設置 Cookie
的名稱及相對應的值,對於認證 Cookie
,Value
值包括 Web
服務器所提供的訪問令牌 。
5.2 Domain
指定了可以訪問該 Cookie
的 Web 站點或域。
Cookie
機制並未遵循嚴格的同源策略,允許一個子域可以設置或獲取其父域的 Cookie
。
當需要實現單點登錄方案時,Cookie
的上述特性非常有用,然而也增加了 Cookie
受攻擊的危險,比如攻擊者可以藉此發動會話定置攻擊。因而,瀏覽器禁止在 Domain
屬性中設置 .org、.com 等通用頂級域名、以及在國家及地區頂級域下注冊的二級域名,以減小攻擊發生的範圍。
5.3 Path
Path
標識指定了主機下的哪些路徑可以接受 Cookie
(該 URL 路徑必須存在於請求 URL 中)。以字符 %x2F ("/")
作爲路徑分隔符,子路徑也會被匹配。
5.4 Expires/Max-Age
設置 Cookie
的生存期。有兩種存儲類型的 Cookie
:會話性與持久性。
Expires
屬性指定一個具體的到期時間,到了這個指定的時間之後,瀏覽器就不再保留這個 Cookie
,它的值是 UTC 格式,可以使用 Date.prototype.toUTCString()
格式進行轉換。
Max-Age
屬性制定了從現在開始 Cookie
存在的秒數,比如 60 * 60 * 24 * 365(即一年)。過了這個時間以後,瀏覽器就不再保留這個 Cookie
如果沒有設置這兩個選項,則會使用默認值。
domain
的默認值爲設置該Cookie
的網頁所在的域名,path
默認值爲設置該Cookie
的網頁所在的目錄。
Expires
屬性缺省時,爲會話性Cookie(Session Cookie)
,僅保存在客戶端內存中,並在用戶關閉瀏覽器時失效。- 持久性
Cookie
會保存在用戶的硬盤中,直至生存期到或用戶直接在網頁中單擊“註銷”等按鈕結束會話時纔會失效。
當
Cookie
的過期時間被設定時,設定的日期和時間只與客戶端相關,而不是服務端。
5.5 HTTPOnly
這個選項用來設置 Cookie
是否能通過 JavaScript
去訪問。默認情況下, Cookie
不會帶 HTTPOnly
選項(即爲空),所以默認情況下,客戶端是可以通過 JavaScript
代碼去訪問(包括讀取、修改、刪除等)這個 Cookie
的。當 Cookie
帶 HTTPOnly
選項時,客戶端則無法通過js代碼去訪問(包括讀取、修改、刪除等)這個 Cookie
。
用於防止客戶端腳本通過 document.cookie
屬性訪問 Cookie
,有助於保護 Cookie
不被跨站腳本攻擊竊取或篡改。但是,HTTPOnly
的應用仍存在侷限性,一些瀏覽器可以阻止客戶端腳本對 Cookie
的讀操作,但允許寫操作;此外大多數瀏覽器仍允許通過 XMLHTTP
對象讀取 HTTP
響應中的 Set-Cookie
頭 。
在客戶端是不能通過
JAvaScript
代碼去設置一個httpOnly
類型的Cookie
的,這種類型的Cookie
只能通過服務端來設置。
5.6 Secure
指定是否使用 HTTPS
安全協議發送 Cookie
。
使用 HTTPS
安全協議,可以保護 Cookie
在瀏覽器和 Web
服務器間的傳輸過程中不被竊取和篡改。該方法也可用於 Web
站點的身份鑑別,即在 HTTPS
的連接建立階段,瀏覽器會檢查 Web
網站的 SSL
證書的有效性。
但是基於兼容性的原因(比如有些網站使用自簽署的證書)在檢測到 SSL
證書無效時,瀏覽器並不會立即終止用戶的連接請求,而是顯示安全風險信息,用戶仍可以選擇繼續訪問該站點。由於許多用戶缺乏安全意識,因而仍可能連接到 Pharming
攻擊所僞造的網站 。
如果當前協議是 HTTP,瀏覽器會自動忽略服務器發來的 Secure。
5.7 SameSite
Cookie
允許服務器要求某個 Cookie
在跨站請求時不會被髮送,(其中 Site
由可註冊域定義),從而可以阻止跨站請求僞造攻擊(CSRF
)。
SameSite cookies
是相對較新的一個字段,所有主流瀏覽器都已經得到支持。下面是例子:
Set-Cookie: key=value; SameSite=Strict
SameSite
可以有下面三種值:
None
瀏覽器會在同站請求、跨站請求下繼續發送Cookies
,不區分大小寫。Strict
瀏覽器將只在訪問相同站點時發送Cookie
。(在原有Cookies
的限制條件上的加強)。Lax
與Strict
類似,但用戶從外部站點導航至URL時(例如通過鏈接)除外。 在新版本瀏覽器中,爲默認選項,Same-site cookies
將會爲一些跨站子請求保留,如圖片加載或者frames
的調用,但只有當用戶從外部站點導航到URL
時纔會發送。如 link 鏈接。
以前,如果
SameSite
屬性沒有設置,或者沒有得到運行瀏覽器的支持,那麼它的行爲等同於None
,Cookies
會被包含在任何請求中——包括跨站請求。大多數主流瀏覽器正在將
SameSite
的默認值遷移至Lax
。如果想要指定Cookies
在同站、跨站請求都被髮送,現在需要明確指定SameSite
爲None
。
5.8 Cookie prefixes
Cookie
機制的使得服務器無法確認 Cookie
是在安全來源上設置的,甚至無法確定 Cookie
最初是在哪裏設置的。
子域上的易受攻擊的應用程序可以使用 Domain
屬性設置 Cookie
,從而可以訪問所有其他子域上的該 Cookie
。會話定置攻擊中可能會濫用此機制。
但是,作爲 深度防禦措施
,可以使用 Cookie
前綴來斷言有關 Cookie
的特定事實。有兩個前綴可用:
-
__Host-
如果
Cookie
名稱具有此前綴,則僅當它也用Secure
屬性標記,是從安全來源發送的,不包括Domain
屬性,並將Path
屬性設置爲/
時,它纔在Set-Cookie
標頭中接受。這樣,這些Cookie
可以被視爲 "domain-locked
”。 -
__Secure-
如果
Cookie
名稱具有此前綴,則僅當它也用Secure
屬性標記,是從安全來源發送的,它纔在Set-Cookie
標頭中接受。該前綴限制要弱於__Host-
前綴。
帶有這些前綴點 Cookie
, 如果不符合其限制的會被瀏覽器拒絕。請注意,這確保瞭如果子域要創建帶有前綴的 Cookie
,那麼它將要麼侷限於該子域,要麼被完全忽略。由於應用服務器僅在確定用戶是否已通過身份驗證或 CSRF 令牌正確時才檢查特定的 Cookie
名稱,因此,這有效地充當了針對會話劫持的防禦措施。
Cookie
各個屬性的兼容性如下圖所示:
6. HTTP Cookie 和 document.cookie
6.1 HTTP Cookie
服務器如果希望在瀏覽器保存 Cookie
,就要在 HTTP
迴應的頭信息裏面,放置一個Set-Cookie
字段。
瀏覽器收到響應後通常會保存下 Cookie
,之後對該服務器每一次請求中都通過 Cookie
請求頭部將 Cookie
信息發送給服務器。另外,Cookie
的過期時間、域、路徑、有效期、適用站點都可以根據需要來指定。
HTTP
迴應可以包含多個 Set-Cookie
字段,即在瀏覽器生成多個 Cookie
。下面是一個例子。
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[page content]
除了 Cookie
的值,Set-Cookie
字段還可以附加 Cookie
的屬性。
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
一個 Set-Cookie
字段裏面,可以同時包括多個屬性,沒有次序的要求。
如果服務器想改變一個早先設置的
Cookie
,必須同時滿足四個條件:Cookie
的key
、domain
、path
和secure
都匹配。否則,會創建一個新的Cookie
。
瀏覽器接收了響應頭提供的 Cookie
之後,每一次訪問該域時,都會攜帶該 Cookie
值:
Cookie
字段可以包含多個 Cookie,使用分號(;
)分隔。
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
6.2 document.cookie
通過 document.cookie
屬性可創建新的 Cookie
,也可通過該屬性訪問==非 HttpOnly== 標記的 Cookie
。
上圖從 document.cookie
一次性讀出多個 Cookie
,它們之間使用分號分隔。必須手動還原,才能取出每一個 Cookie
的值。
寫入的時候,Cookie
的值必須寫成 key=value
的形式。注意,等號兩邊不能有空格。另外,寫入 Cookie
的時候,必須對分號、逗號和空格進行轉義(它們都不允許作爲 Cookie
的值),這可以用 encodeURIComponent
方法達到。比如,我們要存儲一個對象到 Cookie
中,可以通過下面代碼實現:
設置完成後,在瀏覽器查看:
那要怎麼才能讀取到這次設置的 Cookie
呢?方法如下:
讀取到的結果如下:
document.cookie
一次只能寫入一個Cookie
,而且寫入並不是覆蓋,而是添加。
7. Cookie 的安全隱患
信息被存在
Cookie
中時,需要明白Cookie
的值時可以被訪問,且可以被終端用戶所修改的。根據應用程序的不同,可能需要使用服務器查找的不透明標識符,或者研究諸如JSON Web Tokens
之類的替代身份驗證/機密機制。當機器處於不安全環境時,切記不能通過
HTTP Cookie
存儲、傳輸敏感信息。
7.1 Cookie 捕獲/重放
攻擊者可以通過木馬等惡意程序,或使用跨站腳本攻擊等手段偷竊存放在用戶硬盤或內存中的 Cookie
。藉助網絡攻擊手段,包括:
- 在不安全的局域網中被動地監聽網絡通信;
- 通過攻擊網絡用戶的路由器,或通過搭建惡意的無線路由器等手法,控制路由基礎設施,將網絡流量重定向到攻擊者控制的主機;
- 發動
DNSPharming
(域欺騙)攻擊,通過DNS 緩存中毒
、DNS 應答欺騙
、或修改用戶端的本地域名解析文件等方法攻擊DNS
系統,導致用戶對合法網站的訪問請求被重定向到惡意網站等等,同樣可能竊取Cookie
。
對於捕獲到的認證 Cookie
,攻擊者往往會猜測其中的訪問令牌,試圖獲取會話ID、用戶名與口令、用戶角色、時間戳等敏感信息;或者直接重放該 Cookie
,假冒受害者的身份發動攻擊 。
7.2 惡意 Cookies
Cookies
是文本文件, 一般情況下認爲它不會造成安全威脅。 但是,如果在 Cookies
中通過特殊標記語言,引入可執行代碼,就很可能給用戶造成嚴重的安全隱患。HTML
爲區別普通文本和標記語言,用符號 “<>”
來指示 HTML
代碼。 這些 HTML
代碼或者定義 Web
網頁格式,或者引入 Web
瀏覽器可執行代碼段。 Web
服務 器可以使用 Cookies
信息創建動態網頁。假使 Cookies
包含可執行惡意代碼段,那麼在顯示合成有該 Cookies
的網頁時,就會自動執行這段惡意代碼。當然,惡意代碼能否真正造成危害,還取決於 Web
站點的安全配置策略 。
7.3 會話定置
會話定置(Session Fixation
)攻擊是指,攻擊者向受害者主機注入自己控制的認證 Cookie
等信息,使得受害者以攻擊者的身份登錄網站,從而竊取受害者的會話信息。
注入 Cookie
的方法包括:
- 使用跨站腳本或木馬等惡意程序;
- 或僞造與合法網站同域的站點,並利用各種方法欺騙用戶訪問該仿冒網站,從而通過HTTP響應中的Set-Cookie頭將攻擊者擁有的該域Cookie發送給用戶等。
7.4 CSRF 攻擊
跨站請求僞造(Cross-Site Request Forgery
,簡稱CSRF
)是指:
攻擊者可能利用網頁中的惡意代碼強迫受害者瀏覽器向被攻擊的 Web
站點發送僞造的請求,篡奪受害者的認證 Cookie
等身份信息,從而假冒受害者對目標站點執行指定的操作。
Firefox、Opera 等瀏覽器使用單進程機制,多個窗口或標籤使用同一個進程,共享 Cookie
等會話數據。IE 則混合使用單進程與多進程模式,一個窗口中的多個標籤,以及使用 “CTRL+N” 或單擊網頁中的鏈接打開的新窗口使用同一進程,共享會話數據;只有直接運行IE可執行程序打開窗口時,纔會創建新的進程。Chrome 雖然使用多進程機制,然而經測試發現,其不同的窗口或標籤之間仍會共享會話數據,除非使用隱身訪問方式。
因而,用戶同時打開多個瀏覽器窗口或標籤訪問互聯網資源時,就爲 CSRF
攻擊篡奪用戶的會話 Cookie
創造了條件。另外,如果一個Web 站點提供持久化 Cookie
,則 CSRF
攻擊將更直接、更容易。
緩解 Cookie 攻擊的方法如下:
- 對用戶輸入進行過濾來阻止 XSS;
- 任何敏感操作都需要確認;
- 用於敏感信息的 Cookie 只能擁有較短的生命週期;
8. 安全使用 Cookie
有兩種方法可以確保 Cookie
被安全發送,並且不會被意外的參與者或腳本訪問:Secure
屬性和 HttpOnly
屬性。
標記爲 Secure
的 Cookie
只應通過被 HTTPS
協議加密過的請求發送給服務端,因此可以預防 man-in-the-middle
攻擊者的攻擊。但即便設置了 Secure
標記,敏感信息也不應該通過 Cookie
傳輸,因爲 Cookie
有其固有的不安全性,Secure
標記也無法提供確實的安全保障, 例如,可以訪問客戶端硬盤的人可以讀取它。
JavaScript Document.cookie API
無法訪問帶有 HttpOnly
屬性的 Cookie
;此類 Cookie
僅作用於服務器。例如,例如,持久化服務器端會話的 Cookie
不需要對 JavaScript
可用,而應具有 HttpOnly
屬性。此預防措施有助於緩解跨站點腳本(XSS
)攻擊。
9. Cookie 的替代方案
由於 Cookie
在使用上存在較多限制,近年來,隨着技術的發展成熟,出現了幾種可替代 Cookie
的方案,且已被大多數主流瀏覽器支持。
- Web Storage、window.localStorage
在瀏覽器中存儲數據的另一種方法是 ==Web Storage API==。window.sessionStorage
和 window.localStorage
屬性與持續時間中的會話和永久 Cookie
相對應,但是存儲限制比 Cookie
大,並且永遠不會發送到服務器。
- IndexedDB
可以使用 IndexedDB API
或基於它構建的庫來存儲更多結構化的數據。
- Web SQL
Web SQL
是一種利用數據庫進行數據存儲並利用 SQL 處理檢索任務的 API。
歡迎大家來到我的「山頭」,我是「前端三昧」的作者 隱逸王 —— 一個想要做山大王的男人!
願和你一起領略前端三昧,發現前端之美!