@
什麼是Cookie?
Cookie
就是訪問者在訪問網站後留下的一個資訊片段。它儲存在客戶端(通常來說是瀏覽器)。你可以把cookie
當作一個map
,裡邊是鍵值對,每個鍵值對有過期時間、域、路徑、指令碼可否訪問等描述資訊;描述資訊儲存在客戶端,客戶端請求時,預設會帶上cookie
的名稱和值,不會帶描述資訊,通過http
請求報文header
中的cookie
項進行傳輸;伺服器響應時,可以設定cookie
資訊,就在http
響應報文的header
中Set-Cookie
項。
那麼,它究竟有什麼作用呢?
Cookie 的作用
眾所周知,HTTP
協議是無狀態的協議,如果你在同一個客戶端向伺服器傳送多次請求,伺服器不會知道這些請求來自同一客戶端(同一個使用者)。
這樣有什麼好處呢?如果它是有狀態協議,你必須要時刻與伺服器建立連結,那麼如果連線意外斷開,整個會話就會丟失,重新連線之後一般需要從頭開始;而如果是無狀態協議,使得會話與連線本身獨立起來,這樣即使連線斷開了,會話狀態也不會受到嚴重傷害,保持會話也不需要保持連線本身。
但是,缺點也很明顯:即使同一個客戶端連續兩次傳送請求給伺服器,伺服器也識別不出這是同一個客戶端傳送的請求,這導致的問題就比如你加了一個商品到購物車中,但因為識別不出是同一個客戶端,你重新整理下頁面就沒有了。
為了使伺服器知道每個請求具體來自於哪個使用者,比如你在逛淘寶的時候你只需要登入一次,當你發起一次購買請求,伺服器就已經知道你登入過了,不會再讓你進行登入。
由此,Cookie
誕生了,Cookie
就是一種瀏覽器管理狀態的一個檔案,讓無狀態的 HTTP
協議擁有一小塊記憶。
HTTP Cookie 機制是 HTTP 協議無狀態的一種補充和改良
Cookie
主要有以下用途:
會話管理
:登陸、購物車等應該記住的其他內容
-
個性化
:使用者偏好、主題或者其他設定 -
追蹤
:記錄和分析使用者行為
Cookie原理
第一次訪問網站的時候,瀏覽器發出請求,伺服器響應請求後,會將cookie
放入到響應請求中(就在http
響應報文的header
中Set-Cookie
項),在瀏覽器第二次發請求的時候,會把cookie
帶過去(http
請求報文header
中的cookie
項),服務端會辨別使用者身份,當然伺服器也可以修改cookie
內容。
Set-Cookie 和 Cookie 標頭
Set-Cookie
HTTP 響應標頭將 cookie 從伺服器傳送到使用者代理。下面是一個傳送 Cookie 的例子
隨著對伺服器的每個新請求,瀏覽器將使用 Cookie
頭將所有以前儲存的 Cookie
傳送回伺服器。
Cookie的分類
有兩種型別的 Cookies
,一種是 Session Cookies
,一種是 Persistent Cookies
,如果 Cookie
不包含到期日期,則將其視為會話 Cookie
。會話 Cookie
儲存在記憶體中,永遠不會寫入磁碟,當瀏覽器關閉時,此後 Cookie
將永久丟失。如果 Cookie
包含有效期 ,則將其視為永續性 Cookie
。在到期指定的日期,Cookie
將從磁碟中刪除。還有一種是 Cookie
的 Secure
和 HttpOnly
標記,後面會介紹。
會話 Cookies
會話 Cookie
有個特徵,客戶端關閉時 Cookie
會刪除,因為它沒有指定Expires
或 Max-Age
指令。
但是,Web 瀏覽器可能會使用會話還原,這會使大多數會話 Cookie
保持永久狀態,就像從未關閉過瀏覽器一樣。
例如:
永久性 Cookies
永久性 Cookie
不會在客戶端關閉時過期,而是在特定日期(Expires)
或特定時間長度(Max-Age)
外過期。例如:
Cookie 的屬性
name
cookie
的名字,一個域名下繫結的cookie
,name
不能相同,相同的name
的值會被覆蓋掉
value
value
表示cookie
的值
【注】用 JavaScript
操作 Cookie
的時候注意對 value
進行編碼處理。
Domain
這個代表的是,cookie
繫結的域名,如果沒有設定,就會自動繫結到執行語句的當前域。由於同源策略,指令碼只能訪問父域名或本域名的cookie
(瀏覽器只能傳送父域名或本域名的cookie
),比如設定cookie
域名為一級域名mydomain.com
;那麼此域名下的二級域名www.mydomain.com
,images.mydomain.com
頁面,都可以訪問此cookie
【注】cookie
區分域,而不區分埠,也就是說,同一個ip
下的多個埠下的cookie
是共享的!更確切的說,請求是會帶上同域下所有埠的cookie
,但是返回時會覆蓋。
例如以下栗子:
Path
Path
這個屬性預設是'/'
,這個值匹配的是web
的路由
cookie
是區分路徑的,也就是說,同一個鍵值對可以同時設定到 www.mydomain.com,www.mydomain.com/b下,並且是獨立的,互不影響的;如果不指定路徑,預設是當前路徑。
Domain
和 Path
標識共同定義了 Cookie
的作用域:即 Cookie
應該傳送給哪些 URL
。
Expires
Expires
用於設定 Cookie
的過期時間。當 Expires
屬性缺失時,表示是會話性 Cookie
,值儲存在客戶端記憶體中,並在使用者關閉瀏覽器時失效。需要注意的是,有些瀏覽器提供了會話恢復功能,這種情況下即使關閉了瀏覽器,會話期 Cookie
也會被保留下來,就好像瀏覽器從來沒有關閉一樣。
與會話性 Cookie
相對的是永續性 Cookie
,永續性 Cookies
會儲存在使用者的硬碟中,直至過期或者清除 Cookie
。這裡值得注意的是,設定的日期和時間只與客戶端相關,而不是服務端。
所以如果你想要cookie
存在一段時間,那麼你可以通過設定Expires
屬性為未來的一個時間節點,Expires
這個是代表當前時間的,然而這個屬性已經逐漸被Max-Age
代替。
Max-Age
Max-Age
用於設定在 Cookie
失效之前需要經過的秒數。
Max-Age
可以為正數、負數、甚至是 0。
-
當
Max-Age
屬性為正數時,瀏覽器會將其持久化,即寫到對應的Cookie
檔案中。 -
當
Max-Age
屬性為負數,則表示該Cookie
只是一個會話性Cookie
。 -
當
Max-Age
為 0 時,則會立即刪除這個Cookie
。因為cookie
機制本身沒有設定刪除cookie
,失效的cookie
會被瀏覽器自動從記憶體中刪除,所以,它實現的就是讓cookie
失效。
【注】假如 Expires
和 Max-Age
都存在,Max-Age
優先順序更高。
Secure
由於http
不僅是無狀態的,還是不安全的協議,容易被劫持。所以標記為 Secure
的 Cookie
只應通過被https
協議加密過的請求傳送給服務端。使用 https
安全協議,可以保護 Cookie
在瀏覽器和 Web
伺服器間的傳輸過程中不被竊取和篡改。
HTTPOnly
如果這個屬性設定為true
,就不能通過js
指令碼來獲取cookie
的值,能有效的防止xss
攻擊。
SameSite
Chrome 51
開始,瀏覽器的 Cookie
新增加了一個SameSite
屬性,用來防止 CSRF
攻擊和使用者追蹤。
Cookie
的SameSite
屬性可以設定三個值:
- Strict
- Lax
- None
Strict
Strict
最為嚴格,完全禁止第三方 Cookie
,跨站點時,任何情況下都不會傳送 Cookie
。換言之,只有當前網頁的 URL
與請求目標一致,才會帶上 Cookie
。
這個規則過於嚴格,可能造成非常不好的使用者體驗。比如,當前網頁有一個 GitHub
連結,使用者點選跳轉就不會帶有 GitHub
的 Cookie
,跳轉過去總是未登陸狀態。
Lax
Lax
規則稍稍放寬,大多數情況也是不傳送第三方 Cookie
,但是導航到目標網址的 Get
請求除外。
導航到目標網址的 GET 請求,只包括三種情況:連結,預載入請求,GET 表單。詳見下表。
請求型別 | 示例 | 正常情況 | Lax |
---|---|---|---|
連結 | <a href="..."></a> |
傳送 Cookie | 傳送 Cookie |
預載入 | <link rel="prerender" href="..."/> |
傳送 Cookie | 傳送 Cookie |
GET 表單 | <form method="GET" action="..."> |
傳送 Cookie | 傳送 Cookie |
POST 表單 | <form method="POST" action="..."> |
傳送 Cookie | 不傳送 |
iframe | <iframe src="..."></iframe> |
傳送 Cookie | 不傳送 |
AJAX | $.get("...") |
傳送 Cookie | 不傳送 |
Image | <img src="..."> |
傳送 Cookie | 不傳送 |
設定了Strict
或Lax
以後,基本就杜絕了 CSRF 攻擊。當然,前提是使用者瀏覽器支援 SameSite 屬性。
None
Chrome
已將Lax
變為預設設定。這時,網站可以選擇顯式關閉SameSite
屬性,將其設為None
。不過,前提是必須同時設定Secure
屬性(Cookie
只能通過 https
協議傳送),否則無效。
Cookie的讀寫
document.cookie
屬性用於讀寫當前網頁的 Cookie
。
讀取的時候,它會返回當前網頁的所有 Cookie
,前提是該 Cookie
不能有HTTPOnly
屬性。
document.cookie // "foo=bar;baz=bar"
document.cookie
屬性是可寫的,可以通過它為當前網站新增 Cookie
。
寫入的時候,Cookie
的值必須寫成key=value
的形式。注意,等號兩邊不能有空格。另外,寫入 Cookie
的時候,必須對分號、逗號和空格進行轉義(它們都不允許作為 Cookie
的值),這可以用encodeURIComponent
方法達到。
但是,document.cookie
一次只能寫入一個 Cookie
,而且寫入並不是覆蓋,而是新增。
document.cookie = 'test1=hello';
document.cookie = 'test2=world';
document.cookie
// test1=hello;test2=world
document.cookie
讀寫行為的差異(一次可以讀出全部 Cookie
,但是隻能寫入一個 Cookie
),與 HTTP
協議的 Cookie
通訊格式有關。瀏覽器向伺服器傳送 Cookie
的時候,Cookie
欄位是使用一行將所有 Cookie
全部傳送;伺服器向瀏覽器設定 Cookie
的時候,Set-Cookie
欄位是一行設定一個 Cookie
。
寫入 Cookie
的時候,可以一起寫入 Cookie
的屬性。
document.cookie = "test=hello; expires=Fri, 31 Dec 2020 23:59:59 GMT";
上面程式碼中,寫入 Cookie
的時候,同時設定了expires
屬性。屬性值的等號兩邊,也是不能有空格的。
各個屬性的寫入注意點如下。
path
屬性必須為絕對路徑,預設為當前路徑。domain
屬性值必須是當前傳送Cookie
的域名的一部分。比如,當前域名是example.com
,就不能將其設為foo.com
。該屬性預設為當前的一級域名(不含二級域名)。max-age
屬性的值為秒數。expires
屬性的值為UTC
格式,可以使用Date.prototype.toUTCString()
進行日期格式轉換。
document.cookie
寫入 Cookie
的例子如下。
document.cookie = 'fontSize=14; '
+ 'expires=' + someDate.toGMTString() + '; '
+ 'path=/subdirectory; '
+ 'domain=*.example.com';
Cookie
的屬性一旦設定完成,就沒有辦法讀取這些屬性的值。
刪除一個現存 Cookie
的唯一方法,是設定它的expires
屬性為一個過去的日期。或者上面提到的Max-Age
屬性。
document.cookie = 'fontSize=;expires=Thu, 01-Jan-1970 00:00:01 GMT';
上面程式碼中,名為fontSize
的 Cookie
的值為空,過期時間設為1970年1月1月零點,就等同於刪除了這個 Cookie
。
參考: