真正“搞”懂HTTP協議09之這個餅乾不能吃

Zaking發表於2023-01-17

  我們在之前的文章中介紹HTTP特性的時候聊過,HTTP是無狀態的,每次聊起HTTP特性的時候,我都會回憶一下從前輝煌的日子,也就是網際網路變革的初期,那時候其實HTTP不需要有狀態,就是個瀏覽頁面,沒有什麼需要記錄資訊的地方,所以無狀態完全符合當時的場景。

  另外,無狀態也給HTTP帶來了不少的好處,正是因為無狀態,這樣伺服器就沒有狀態差異,就可以很輕易的組成叢集,當然,缺點就是無法支援需要記錄狀態的事務。為了解決這個缺點,Cookie就出現了。

一、這個餅乾是什麼?

  Cookie的核心作用,其實就是讓HTTP擁有記憶的能力,雖然伺服器記不住,但是伺服器可以根據HTTP提供的資訊來做出相應的邏輯和判斷。你大概可以這樣理解,相當於伺服器給每一個客戶端都貼上了一個小紙條,當伺服器把紙條設定好後,會傳送給客戶端,客戶端每次傳輸HTTP資料的時候,就會把這個小紙條帶上發給伺服器,伺服器就可以見人下菜碟了。

二、小餅乾是怎麼工作的?

  關於Cookie的相關規範,並不在RFC2616或者RFC7230中,而是在RFC6265中,這份規範針對Cookie做了獨立、詳細的介紹。

  Cookie的工作過程主要應用到了兩個欄位:響應頭欄位Set-Cookie和請求頭欄位Cookie

  當客戶端像伺服器第一次請求資源的時候,伺服器會使用Set-Cookie來給客戶端貼上一個標籤,格式就是“key=value”這樣,隨著響應報文一起傳送給瀏覽器。這樣瀏覽器在下一次傳送請求的時候會自動帶上cookie資料,伺服器發現欸?這次請求有Cookie,就知道不是首次請求,然後識別Cookie,為該使用者提供個性化的服務。

  當然,我們還可以使用多個Set-Cookie欄位,來設定多個資料,讓客戶端攜帶更多的有用的資訊。

  要注意的是,Cookie與作業系統無關,是瀏覽器繫結的,當你換了瀏覽器,實際上相當於一個新的初次請求。

三、Cookie的屬性

  我們現在知道了,Cookie其實就是伺服器委託客戶端儲存的一些資料,通常這些資料都是用來記錄使用者的關鍵資訊。那麼就需要一些額外的手段來保證Cookie的安全,這些手段就是Cookie的屬性。我們下面就來看一下這些常用的有關於Cookie的屬性有哪些。

  首先,我們可以透過ExpiresMax-Age設定Cookie的有效期,“Expires”俗稱“過期時間”,用的是絕對時間點,可以理解為“截止日期”(deadline)。“Max-Age”用的是相對時間,單位是秒,瀏覽器用收到報文的時間點再加上 Max-Age,就可以得到失效的絕對時間。這兩者可以同時出現,兩者的失效時間可以一致,也可以不一致,但瀏覽器會優先採用 Max-Age 計算失效期

  其次,我們還可以設定Cookie的作用域,“Domain”和“Path”指定了 Cookie 所屬的域名和路徑,瀏覽器在傳送 Cookie 前會從 URI 中提取出 host 和 path 部分,對比 Cookie 的屬性。如果不滿足條件,就不會在請求頭裡傳送 Cookie。

  使用這兩個屬性可以為不同的域名和路徑分別設定不同的Cookie,比如/a用一個Cookie,/b用另外一個Cookie,當然通常都是一個“/”就完事了。

  最後,我們要考慮的就是Cookie的安全性了,前端的同學們肯定知道,Cookie可以透過document.cookie獲取,這樣就導致了安全隱患,可能會導致XSS也就是跨站指令碼攻擊,從而竊取資料。HttpOnly屬性就會告訴瀏覽器,此Cookie只能透過瀏覽器傳輸,禁止其它方式的訪問,瀏覽器就會禁止任何API對cookie的訪問,從而避免了XSS攻擊。

  再有,SameSite屬性可以防範“跨站請求偽造”(XSRF)攻擊,設定成“SameSite=Strict”可以嚴格限定 Cookie 不能隨著跳轉連結跨站傳送,而“SameSite=Lax”則略寬鬆一點,允許 GET/HEAD 等安全方法,但禁止 POST 跨站傳送。

  還有一個屬性叫“Secure”,表示這個 Cookie 僅能用 HTTPS 協議加密傳輸,明文的 HTTP 協議會禁止傳送。但 Cookie 本身不是加密的,瀏覽器裡還是以明文的形式存在。

四、Cookie的應用場景

  其實Cookie最常見的應用場景就是身份識別,儲存使用者的登入資訊,實現會話事務。

  另一個常見的場景就是廣告追蹤,你上網的時候肯定看過很多的廣告圖片,這些圖片背後都是廣告商網站(例如 Google),它會“偷偷地”給你貼上 Cookie 小紙條,這樣你上其他的網站,別的廣告就能用 Cookie 讀出你的身份,然後做行為分析,再推給你廣告。這種 Cookie 不是由訪問的主站儲存的,所以又叫“第三方 Cookie”(third-party cookie)。如果廣告商勢力很大,廣告到處都是,那麼就比較“恐怖”了,無論你走到哪裡它都會透過 Cookie 認出你來,實現廣告“精準打擊”。為了防止濫用 Cookie 蒐集使用者隱私,網際網路組織相繼提出了 DNT(Do Not Track)和 P3P(Platform for Privacy Preferences Project),但實際作用不大

五、例子

  我們先來看看簡單的Cookie設定。程式碼也很簡單:

res.setHeader("Set-Cookie", ["age=13", "name=zaking"]);

  注意,如果你要設定多個Cookie的話,第二個引數要是個陣列,我們看下效果:

真正“搞”懂HTTP協議09之這個餅乾不能吃

   這是第一次請求,然後我們重新整理下頁面:

真正“搞”懂HTTP協議09之這個餅乾不能吃

   你就能看到請求頭中帶上了cookie,我們還可以給每個cookie設定它的作用域和失效時間:

res.setHeader("Set-Cookie", [
  "age=13; path=/; max-age=5",
  "name=zaking; path=/set-cookie; max-age=10",
  "hide=true; path=/else; max-age=1000",
]);

  記得實驗效果之前把上一次的cookie清空噢。然後我們看上面的程式碼,path限制了路徑,所以當我們訪問/set-cookie的頁面的時候,其實只會有兩個cookie,一個有效時間5秒,一個10秒。大家可以自己試下哦:

真正“搞”懂HTTP協議09之這個餅乾不能吃

   過了這個時間之後,你會發現一個Cookie都沒有了。Cookie的屬性中還有一個限制作用域的屬性,叫做Domain,這個我就不試了,大家可以自行嘗試一下噢。接下來我們看下,我們在頁面中透過document.cookie來獲取Cookie:

<body>
  Cookie
</body>
<script>
  console.log(document.cookie);
</script>

  就這麼簡單,重啟服務重新整理頁面後,可以看到控制檯列印出了作用域範圍內的Cookie。

  我們加個HttpOnly再看看:

res.setHeader("Set-Cookie", [
  "age=13; path=/; max-age=5",
  "name=zaking; path=/set-cookie; max-age=10;HttpOnly",
  "hide=true; path=/else; max-age=1000",
]);

  結果就只剩下age了,完全符合我們的預期。

  那麼例子就到這裡啦,還有一些我沒寫出來噢,比如SameSite和Secure。其實也並不複雜,我就是懶得寫了。

六、總結

  Cookie其實是有大名的,叫做Magic Cookie,意思是不透明的資料,跟餅乾沒啥關係噢。早期的Cookie都是存在磁碟上的文字檔案,現在基本上是使用資料庫儲存,比如sqlite,儲存的大小有一定的限制,是4k。

  如果不指定Cookie生效實踐,則會在瀏覽器關閉後無效,也叫做會話Cookie。歷史上還有Set-Cookie2和Cookie2這樣的欄位,但是現在沒用了。

<body>
Cookie
</body>
<script>
console.log(document.cookie);
</script>

相關文章