[筆記] HTTP Cookies 和 Session 使用

Mike Huang
麥克的半路出家筆記
7 min readJun 30, 2019

HTTP 是一個「無狀態協議 Stateless Protocol」,也就是說,每次從客戶端(Client)對伺服器(Server)發出的請求都是獨立的 — 這一次的請求無法得知上一次請求的內容與資訊。

也因此,最近開始在學習將認證系統加入餐廳清單的應用程式當中時,就遇到了有趣的問題:既然 HTTP 是一個無狀態協議「那伺服器該如何辨認不同的請求是來自同個瀏覽器?」或「使用者登入後,伺服器如何在往後的請求中,辨認使用者其實是已經通過驗證(已登入)的狀態?」 — 透過運用 Cookie 和 Session 可以解決這些問題。

Cookie

Cookie 是伺服器(Server)傳送給瀏覽器(Client)的一小片段資料,並請瀏覽器保存起來,以便往後向相同的伺服器發送請求時,附上這 Cookie 的資料。

Cookie 常見用途

  1. 儲存和追蹤使用者行為
  2. 儲存用戶登入、購物車等伺服器所需的資訊
  3. 儲存使用者設定和偏好等

基本用戶登入流程範例

  1. 使用者透過瀏覽器登入畫面登入
  2. 伺服器端通過驗證後,可以將使用者「已經登入」的資訊附在 Cookie 中回傳,並請瀏覽器保存起來
  3. 往後,每當瀏覽器對伺服器發出請求時,會一併附上存有使用者「已經登入」狀態資訊的 Cookie 給伺服器
  4. 伺服器透過 Cookie 就能辨識這位使用者已經通過驗證了

在 Express 中建立伺服器回傳 Cookie

透過 res.setHeader(‘Set-Cookie’, <cookie-name>=<coolie-value>) 設定回傳 的 Cookie 和內容:

在 DevTools Application 中查看伺服器回傳的 Cookies

查看用戶往後對同個伺服器發出的不同請求中都附上該 Cookie

從下方 Terminal 的結果可以發現,不管是對 /anotherPath 這條路徑,或其他相同伺服器的不同路徑發出請求,從該客戶端向伺服器發出的請求,都會附上包含 isLoggedIn 資訊的 Cookie:

使用 Cookie 傳送重要資訊的安全性隱憂

透過 Cookie,我們的確達成目標,讓伺服器可以在往後透過客戶端發出的請求,辨識使用者及其登入狀態,非常方便。然而,有趣的是,這些資訊其實用戶是有機會可以在瀏覽器中修改的,因此使用者能透過串改 Cookie 上的=內容,讓伺服器收到不正確的訊息 — 也就是說,以登入的例子來看,使用者可以在串改 isLoggedIn 的值,讓伺服器誤以為使用者已經通過認證:

儘量避免將敏感資訊透過 Cookie 存在客戶端

Session

我們了解到存放較敏感的資訊在客戶端是有安全上的疑慮,也因此我們改使用 Session 將使用者相關的敏感資訊存放在伺服器端 — 可能在記憶體或資料庫中 — 並創建一個相對應且獨特的 ID(Session ID),在回傳給客戶端的 Cookie 中一併附上,未來客戶端只要附上含有這個 Session ID 的 Cookie 給伺服器,伺服器就能匹配相對應的 Session — 也能找到需要的敏感資料了!

透過 MagicBand 了解 Cookie 和 Session 間的關係

想像你入住了迪士尼飯店,飯店告知有關你的入房、預約餐廳和遊樂設施的資訊都已經存在迪士尼的系統當中了,並附上一只專屬的 MagicBand,手錶晶片中有系統給你的專屬 ID 號碼,因此這幾天在飯店直接感應房門、在餐廳或遊樂設施前感應機器就能透過系統辨識完成開門和報到。

你的個人資訊很重要,因此迪士尼將入房、預約資訊以 Session 存放在系統中,這筆資料對應了一個獨特的 Session ID。而 MagicBand 就像是 Cookie,當中帶有這個 Session ID,當你透過 MagicBand 在感應機器時,系統就能快速辨識你的身份,並找到存放你入房和預約資料的 Session 了!

如同前面所說的,重要的資訊不建議放在客戶端 — 像是寫在你的 MagicBand 上,你就有機會串改遊樂設施的預約時間、其他人也有機會看到你的隱私等 — 若放在迪士尼的系統中(像是伺服器的概念),就顯得安全許多:你唯一需要的,是那只含有獨特 ID 的 MagicBand 讓系統辨識罷了!

在 Node.js 中使用 Session

安裝 express-session 套件

$ npm install express-session

引入 express-session 套件

建立 session 的 middleware

options 物件中可以帶入的屬性非常多,其中最重要的是 secret secret 的值會被用來做為製造存放在 Cookie 當中的 *hashed session ID,通常是一串很長的字串:

!補充:這邊提到的 hashed session ID  可以想像是把你提供的 secret 字串透過特殊的雜湊演算法(hashing algorithm)產生的一組獨特 ID

儲存和獲取 session data

在瀏覽器送出登入表單後,可以看到多了一組 Cookie,其中的 value 就是 hashed Session ID — 用來讓伺服器辨識和找尋相對應的 Session

為了驗證同個使用者透過客戶端發出的不同請求都會附上含有 Session ID 的 Cookie 給伺服器,且伺服器可以透過 Cookie 上獨特的 Session ID 找到相對應的 Session data,我們在 / 路徑的路由中,將 Session 中 isLoggedIn 的值印出來看看:

從上面登入案例的實驗結果可以了解到:

  1. 重點一:使用者敏感的資訊(例如範例中「已登入狀態」的資訊)將被存放在伺服器端,而不是客戶端 — 因此客戶無法任意在瀏覽器中修改,資料保存也較安全
  2. 重點二:伺服器會將一個獨特的 Session ID 附在回傳的 Cookie 給瀏覽器 — 未來伺服器只要透過這個 Session ID,就能找到相對應的 Session data(例如範例中使用者提供了這個 Session ID,伺服器就能知道該名使用者已經登入過)
  3. 重點三:有了 Session ID 同個用戶透過客戶端發出的「不同請求」給相同的伺服器時,伺服器都能辨識對方為相同的瀏覽器(例如範例中,客戶端在登入後,對 / 路徑發出請求時,我們能看到 Session 當中 isLoggedIn 的值為 true

結語

在沒有使用 Cookie 和 Session 之前,照理來說,不同的請求都是獨立的,這一次發出的請求是拿不到上一次的資訊。然而透過 Session,我們得以將用戶敏感資訊儲存在伺服器端,搭配 Cookie,伺服器得已透過客戶端每次發出的不同請求,獲取所需要的 Session ID ,找到需要使用的用戶資訊。

補充資訊

更多 Cookies 介紹和設定內容可以參考:MDN HTTP Cookies

--

--

Mike Huang
麥克的半路出家筆記

熱愛接觸和學習網頁開發相關技術與知識、喜歡分享、旅遊和咖啡的軟體工程師 A software engineer who enjoy learning and sharing web technologies & fancy coffee and travelling