Cookie 和 Session 究竟是什麼?有什麼差別?

Cookie 和 Session 這兩個名詞,相信大部分的開發者都不會太陌生,特別是 Cookie,從社群網站、電商平台、Google Analytics 分析等地方,無處不見它的應用;但從本質上,Cookie & Session 究竟是什麼呢?這就要從 HTTP 的特性說起。

無狀態的 HTTP

小明喜歡帶著 MAC 去星巴克當潮潮,每周總是會擠出時間去星巴克喝咖啡用電腦,常常一坐就是一整天;從最一開始不知道要點什麼,逐漸喝到辦了隨行卡,還成為金星級會員;而正妹店員也從詢問「先生貴姓」,轉變成讀取隨行卡後詢問「小明您的隨行卡餘額還有 280 元喔」。

注意到關鍵了嗎?對就是 正妹店員 隨行卡。在沒有隨行卡的情況下,店員不知道小明是誰,對店員來說,每一位客人都是單次的事件,,必須要透過隨行卡,才能知道小明是誰、剩下多少餘額,也就是小明在星巴克的「狀態」。

上篇文章 我們討論了 RESTful API 時,提到了 HTTP 是一個無狀態的通訊協定,就如同星巴克的店員不會記住客人是誰一樣,伺服器不會記住使用者是誰,而是把每一次收到的請求都視為獨立的行為。

如果是純粹展示靜態內容的網站,無狀態不會是個問題,反而是很棒的優點,因為客戶端、伺服器、資料庫都不需要儲存使用者狀態,也就省去了大量的儲存空間;但在複雜的網路應用,例如需要辨別使用者身分時,無狀態的通訊就會是個問題。

一文搞懂 HTTP 和 HTTPS 是什麼?兩者有什麼差別

HTTP 的狀態管理機制

那如果要讓無狀態的 HTTP 能記住使用者是誰,該怎麼做呢?

還記得剛剛的隨行卡嗎?如果店家對每個使用者發出一個會員卡,就能夠透過會員卡來辨別使用者是誰了;同樣的道理,在 HTTP 的規範 – 狀態管理機制 的章節中,規範了一套讓無狀態的 HTTP 能得知使用者狀態的方法。

簡單來說,就是伺服器透過 Header 的屬性 Set-Cookie,把使用者的狀態紀錄成儲存在使用者電腦裡的 Cookie,而瀏覽器在每一次發送請求時,都在 Header 中設定 Cookie 屬性,把 Cookie 帶上,伺服器就能藉由檢視 Cookie 的內容,得知瀏覽器使用者的狀態;而像是「從登入到登出」、「從開始瀏覽網頁到 Cookie 失效」,或是任何伺服器能認出使用者狀態的時間區間,就叫做 Session

例如瀏覽器回應的內容如下:

而瀏覽器就會依照 Set-Cookie 的內容,建立、儲存指定的鍵值對(key-value pair);當瀏覽器要發送請求時,就會將 Cookies 帶上:

另外,前端開發者可以透過 document.cookie,在 JavaScript 中取得當前的 Cookies:

關於更多規範提到的詳細內容,推薦可以參考 Huli 的淺談 Session 與 Cookie:一起來讀 RFC

Cookie 的屬性

Cookie 除了單純的鍵值對之外,伺服器也可以在 Set-Cookie 內標註這組資料的額外屬性:

  • domain:Cookie 的有效 domain,如果未設定,就會自動綁在執行 Set-Cookie 的 domain 下;雖然可以自行設置,但其實也只能在一級/次級網域之間調整,寫到其他人的 domain 是寫不進去的。
  • path:可以指定 Cookie 只在特定路徑下生效,未設定預設為 ‘/’
  • Max-Age:有效期限,單位是秒;當數值為正數時有效,負數時為本次 Session 有效;0 為刪除 Cookie
  • Expires:同上,只是指定的是時間點
  • secure:安全,當這個屬性被設為 true 時,此 Cookie 就只會在「安全的協議」下傳輸,通常為 HTTPS
  • HttpOnly:只能在網路傳輸中使用,當設為 true 時,此 Cookie 就無法在任何 JavaScript 程式碼中取得

由於有 domain 值的設定,Cookie 是有指定使用範圍的;僅會在請求的目標網域為 Cookie 設定的 domain 時被帶上;透過 domainpath 限縮 Cookie 的使用範圍,以及 Max-Age 指定有效期限,建立在 HTTP 這樣無狀態協議上的應用程式,也就得以獲取、控制使用者狀態了。

小提醒一下,有個容易讓人搞混的名詞,叫做「Session Cookies」,指的是沒有指定 ExpiresMax-Age 的 Cookies,當瀏覽器關閉時,這些 Cookies 也會跟著消失,故得名。

Cookie 的限制

由於 Cookie 是有大小數量限制的,單個最大 4K,一個 domain 下最多設置 20 個,不太可能將全部的使用者資料都儲存在瀏覽器端;同時,也因為對目標網域的每個請求都會帶上 Cookies,開發時千萬不要在 Cookie 中存入大量的資料,否則累計下來冗餘的資料傳輸非常可觀。

如果需要儲存更多的資料,或是省下每次傳輸都把 Cookie 帶好帶滿的流量浪費,就需要換成其他的實作方式了。實務上最常見的作法,是在 Cookie 中只存放能代表使用者是誰的 ID,而伺服器端則另外儲存每個 ID 的使用者狀態,當伺服器在收到請求時,藉由 Cookie 中的 ID,對應回使用者的狀態上,這樣就能認出使用者的身分了。

透過這樣簡單的機制,Cookie 的大小限制就不是問題了,但這種從 Cookie 中的 ID 換取狀態的模式,ID 如果被其他人獲取,使用者身分不就被他人使用了嗎?

對,確實有可能會被他人使用。但無論什麼方式,只要是需要知道使用者身分,使用者端自然會需要儲存資料;而只要是需要儲存資料,就都會有被盜取的風險。所以前面提到的 Cookie 屬性中才會有 secureHttpOnly,甚至實驗中的屬性 SameSite 等等,透過正確的設置它們,來降低潛在的風險,盡可能的保護 Cookie 的存取權限,並提高傳輸過程的安全性。

結語

Cookie 及 Session 是網頁應用不可或缺的機制之一,特別是在前端越來越複雜的現代網站上,如何在無狀態的 HTTP 上得知使用者的身分,並接續使用者狀態,是非常重要的一環;但儲存身分的同時,也就代表著身分被冒用的風險,開發者在應用上就需要特別留心注意了。

(本文作者是ALPHA Camp的助教,前端工程師Gary,文章轉載自他的前端三十系列文與學生共同前進的 Gary 助教專訪)

延伸閱讀:

參考資料

3分鐘小測驗,找到你開始學習網路開發的入口