一文了解cookie

clienter發表於2021-11-10

@

什麼是Cookie?

Cookie 就是訪問者在訪問網站後留下的一個資訊片段。它儲存在客戶端(通常來說是瀏覽器)。你可以把cookie當作一個map,裡邊是鍵值對,每個鍵值對有過期時間路徑指令碼可否訪問等描述資訊;描述資訊儲存在客戶端,客戶端請求時,預設會帶上cookie的名稱和值,不會帶描述資訊,通過http請求報文header中的cookie項進行傳輸;伺服器響應時,可以設定cookie資訊,就在http響應報文的headerSet-Cookie項。

那麼,它究竟有什麼作用呢?

眾所周知,HTTP 協議是無狀態的協議,如果你在同一個客戶端向伺服器傳送多次請求,伺服器不會知道這些請求來自同一客戶端(同一個使用者)。

這樣有什麼好處呢?如果它是有狀態協議,你必須要時刻與伺服器建立連結,那麼如果連線意外斷開,整個會話就會丟失,重新連線之後一般需要從頭開始;而如果是無狀態協議,使得會話與連線本身獨立起來,這樣即使連線斷開了,會話狀態也不會受到嚴重傷害,保持會話也不需要保持連線本身。

但是,缺點也很明顯:即使同一個客戶端連續兩次傳送請求給伺服器,伺服器也識別不出這是同一個客戶端傳送的請求,這導致的問題就比如你加了一個商品到購物車中,但因為識別不出是同一個客戶端,你重新整理下頁面就沒有了。

為了使伺服器知道每個請求具體來自於哪個使用者,比如你在逛淘寶的時候你只需要登入一次,當你發起一次購買請求,伺服器就已經知道你登入過了,不會再讓你進行登入。

由此,Cookie誕生了,Cookie就是一種瀏覽器管理狀態的一個檔案,讓無狀態的 HTTP 協議擁有一小塊記憶。

HTTP Cookie 機制是 HTTP 協議無狀態的一種補充和改良

Cookie 主要有以下用途:

  • 會話管理:登陸、購物車等應該記住的其他內容
  • 個性化:使用者偏好、主題或者其他設定

  • 追蹤:記錄和分析使用者行為

Cookie原理

第一次訪問網站的時候,瀏覽器發出請求,伺服器響應請求後,會將cookie放入到響應請求中(就在http響應報文的headerSet-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 將從磁碟中刪除。還有一種是 CookieSecureHttpOnly 標記,後面會介紹。

會話 Cookies

會話 Cookie 有個特徵,客戶端關閉時 Cookie 會刪除,因為它沒有指定ExpiresMax-Age 指令。

但是,Web 瀏覽器可能會使用會話還原,這會使大多數會話 Cookie 保持永久狀態,就像從未關閉過瀏覽器一樣。

例如:

在這裡插入圖片描述

永久性 Cookies

永久性 Cookie 不會在客戶端關閉時過期,而是在特定日期(Expires)特定時間長度(Max-Age)外過期。例如:

在這裡插入圖片描述

name

cookie的名字,一個域名下繫結的cookiename不能相同,相同的name的值會被覆蓋掉

value

value表示cookie的值

【注】JavaScript 操作 Cookie 的時候注意對 value 進行編碼處理。

Domain

這個代表的是,cookie繫結的域名,如果沒有設定,就會自動繫結到執行語句的當前域。由於同源策略,指令碼只能訪問父域名或本域名的cookie(瀏覽器只能傳送父域名或本域名的cookie),比如設定cookie域名為一級域名mydomain.com;那麼此域名下的二級域名www.mydomain.comimages.mydomain.com頁面,都可以訪問此cookie

【注】cookie區分域,而不區分埠,也就是說,同一個ip下的多個埠下的cookie是共享的!更確切的說,請求是會帶上同域下所有埠的cookie,但是返回時會覆蓋。

例如以下栗子:

在這裡插入圖片描述
在這裡插入圖片描述

Path

Path這個屬性預設是'/',這個值匹配的是web的路由

cookie是區分路徑的,也就是說,同一個鍵值對可以同時設定到 www.mydomain.com,www.mydomain.com/b下,並且是獨立的,互不影響的;如果不指定路徑,預設是當前路徑。

DomainPath 標識共同定義了 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失效。

【注】假如 ExpiresMax-Age 都存在,Max-Age 優先順序更高。

Secure

由於http不僅是無狀態的,還是不安全的協議,容易被劫持。所以標記為 SecureCookie 只應通過被https協議加密過的請求傳送給服務端。使用 https 安全協議,可以保護 Cookie 在瀏覽器和 Web 伺服器間的傳輸過程中不被竊取和篡改。

HTTPOnly

如果這個屬性設定為true,就不能通過js指令碼來獲取cookie的值,能有效的防止xss攻擊。

SameSite

Chrome 51 開始,瀏覽器的 Cookie 新增加了一個SameSite屬性,用來防止 CSRF 攻擊和使用者追蹤。

CookieSameSite屬性可以設定三個值:

  • Strict
  • Lax
  • None

Strict

Strict最為嚴格,完全禁止第三方 Cookie,跨站點時,任何情況下都不會傳送 Cookie。換言之,只有當前網頁的 URL 與請求目標一致,才會帶上 Cookie

這個規則過於嚴格,可能造成非常不好的使用者體驗。比如,當前網頁有一個 GitHub 連結,使用者點選跳轉就不會帶有 GitHubCookie,跳轉過去總是未登陸狀態。

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 不傳送

設定了StrictLax以後,基本就杜絕了 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';

上面程式碼中,名為fontSizeCookie 的值為空,過期時間設為1970年1月1月零點,就等同於刪除了這個 Cookie
參考:

相關文章