我們在之前的文章中介紹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的屬性有哪些。
首先,我們可以透過Expires 和 Max-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的話,第二個引數要是個陣列,我們看下效果:
這是第一次請求,然後我們重新整理下頁面:
你就能看到請求頭中帶上了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秒。大家可以自己試下哦:
過了這個時間之後,你會發現一個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這樣的欄位,但是現在沒用了。