# Lesson 9: 守門員的識別證：驗證 - Session vs JWT

在Lesson 7的時候有提到 「HTTP是無狀態性的」，那大家都有豐富的網站使用經驗，如果每次請求都是無狀態，那我們大家如何分辨自己的訂單?自己的購物車呢?

所以其實我們會有 認證身分的方式，所以這篇文章就來介紹：Session vs JWT。

# Session

Session 是一種歷史悠久且穩定的身分識別方式，雖然我們叫Session，但其實多半也會需要依靠Cookie的幫忙，但其實Cookie是可以且容易被竄改的，因此實際上還會有其餘的資安防護手法。

Session像是健身房的置物櫃鑰匙。鑰匙本身沒價值，價值在於它能打開 Server 端對應的那個櫃子。

## 運作模式

我們將使用者資訊在Server上生成Session id 後讓前端寫到Cookie之中，之後的請求瀏覽器會自動把符合條件的Cookie 塞到Request後請求給後端Server，這樣一來我們就可以透過解析cookie中的session\_id方式來讓無狀態的Http 可以包含客製化的條件或資訊。

* 建立會話：當使用者登入成功後，Server 會在記憶體 (或資料庫/Redis) 中建立一筆資料，包含使用者的 ID、權限等資訊。
    
* 發放識別證：Server 生成一個唯一的 `session_id`，並透過 HTTP Response Header 的 `Set-Cookie` 指令，將這個 ID 寫入使用者的瀏覽器 Cookie 中。
    
* 自動攜帶：瀏覽器之後對該 Domain 的每一次請求，都會自動在 Header 帶上這個 Cookie (`session_id`)。
    
* 驗證身分：Server 收到請求後，解析 Cookie 中的 `session_id`，去 Server 端的儲存空間查找對應的資料。如果找得到，就代表驗證通過。
    

### 優點

* 控制權在 Server 手上：如果你想踢掉某個使用者（例如：強制登出），Server 只要把手上的 Session 資料刪除，使用者的 `session_id` 瞬間就會失效。
    
* 資料隱密性高：真正敏感的資訊（如使用者權限、個資）都存在 Server 端，Client 端只拿得到一串沒有意義的亂碼 ID。
    

### 缺點

* Server 負擔較重：每個在線上的使用者都需要佔用 Server 的記憶體或儲存空間。
    
* 擴展性挑戰：當你的網站流量變大，需要增加 Server (Load Balancer) 時，如果 Session 存在 Server A 的記憶體，使用者的下一個請求被導流到 Server B，Server B 會因為找不到 Session 而判定使用者未登入。
    
    * 解決方案：通常需要搭配 Redis 來集中管理 Session。
        
* CSRF 風險：因為瀏覽器會「自動」攜帶 Cookie，這導致了 CSRF (跨站請求偽造) 的攻擊風險(即使 JWT 存在 Cookie，一樣會有 CSRF)
    

## 補充知識：Cookie 的防護罩 (Cookie Attributes)

既然 Session ID 通常是存在 Cookie 裡的，那如果這個 Cookie 被駭客偷走，駭客就能偽裝成你的身分（Session Hijacking）。因此，瀏覽器提供了多個 **Cookie 屬性 (Attributes)** 來限制 Cookie 的存取範圍與傳輸方式，這就是我們保護 Session ID 的第一道防線。

當後端發送 `Set-Cookie` Header 時，通常會長這樣： `Set-Cookie: session_id=xyz123; HttpOnly; Secure; SameSite=Lax; Path=/; Domain=`[`example.com`](http://example.com)

1. HttpOnly (防禦 XSS 的關鍵)
    

* 意義：設定了這個屬性的 Cookie，無法透過 JavaScript (如 `document.cookie`) 讀取。
    
* 為什麼重要：這是防禦 XSS (跨站腳本攻擊) 最重要的設定。假設駭客在你的網站留言版植入了一段惡意 JS 代碼來竊取 Cookie，如果 Session ID 有開啟 `HttpOnly`，駭客的程式碼就讀不到該 Cookie，有效降低帳號被盜用的風險。
    
* 結論：敏感資訊 (如 Session ID) 務必開啟此選項。
    

2. Secure (只許加密傳輸)
    

* 意義：設定此屬性後，Cookie 只能透過 HTTPS 協定傳輸。如果是普通的 HTTP (明文) 請求，瀏覽器就不會帶上這個 Cookie。
    
* 為什麼重要：防止攻擊者在網路節點中進行監聽 (Man-in-the-Middle attack) 攔截到明文的 Cookie。
    
* 結論：在正式環境 (Production) 中，必須開啟。
    

3. Domain & Path (限制作用範圍)
    

* Domain：指定 Cookie 在哪個網域下有效。
    
    * 如果不設定，預設是當前 Domain (不含子網域)。
        
    * 如果設定 `Domain=`[`example.com`](http://example.com)，則 [`api.example.com`](http://api.example.com) 或 [`www.example.com`](http://www.example.com) 等子網域都能讀取到此 Cookie。
        
* Path：指定 URL 路徑下有效。
    
    * 通常設定為 `/` (根目錄)，代表整站都通用。
        
    * 若設定 `/admin`，則只有在進入 `/admin` 開頭的網址時，瀏覽器才會帶上此 Cookie。
        

**⚠️** 注意：Domain 不能隨便設！ 設定 Cookie 的 `Domain` 屬性時，你只能設定「當前網域」或「當前網域的父網域」。 例如 [`api.example.com`](http://api.example.com) 可以設 `Domain=.`[`example.com`](http://example.com)，但不能設 `Domain=`[`google.com`](http://google.com)。 如果你的前後端網域完全不同 (例如前後端分離部署在不同平台)，建議改用 JWT，會比硬要設定 Cookie 來的省事許多。

4. SameSite (防禦 CSRF 的新星)
    

* 意義：限制 Cookie 是否可以隨著「跨站請求」發送。這是現代瀏覽器防禦 CSRF (跨站請求偽造) 的重要機制。
    
* 三種模式：
    
    * `Strict` (嚴格模式)：只有「第一方 context」(網址列是原本的網站) 才會發送 Cookie。如果你從 Facebook 點擊連結連回你的網站，因為來源不同，Cookie 不會被發送 (導致使用者看起來像沒登入)。安全性最高，但使用者體驗較差。
        
    * `Lax` (寬鬆模式/預設值)：允許部分安全的跨站請求 (如 `GET` 導航) 攜帶 Cookie。例如從外部點連結進入你的網站，可以保持登入狀態；但如果是外部表單 `POST` 你的 API，Cookie 就不會帶上。這是目前大多數瀏覽器的預設值。
        
    * `None` (無限制)：無論同站或跨站都會發送 Cookie。但現代瀏覽器強制要求設定 `SameSite=None` 時，必須同時設定 `Secure`，否則會被瀏覽器阻擋。
        

### 這裡說明比較容易混淆的 Domain+Path / SameSite

在SameSite的出現非常有效的緩解了CSRF的攻擊。

* Domain + Path：決定 Cookie **「能不能送去哪裡」**。
    
* SameSite：決定 Cookie **「能不能從別人家發出去」**。
    

至於為什麼 SameSite + Domain + Path 可以有效降低CSRF攻擊，我們會在之後的章節提到。

# 關於JWT

JWT 的全稱是：JSON Web Token。隨著前後端分離 (SPA) 與微服務架構的普及，JWT 成為常見的身份驗證機制之一。

JWT 的核心概念是「狀態儲存在 Client 端，Server 僅負責驗證簽名」。Server 不再像 Session 模式那樣儲存登入狀態，而是把資訊打包並「簽名」後發給 Client。

需要強調的是：**JWT 本身不是安全機制，也不一定比 Session 更安全。它只是另一種身份驗證模式。**

JWT 很像護照。護照上寫了你的資料，而海關（Server）只需檢查它是否被篡改（簽名驗證）以及是否過期，不需要再向後端查詢你是誰。

---

## JWT 的資料結構

JWT 由三個部分組成：

1. **Header**（演算法，例如：HS256）
    
2. **Payload**（資料內容，例如：user\_id、nick\_name…）
    
3. **Signature**（簽名，將 Header + Payload + **密鑰 (Secret)** 雜湊後得出）
    

JWT 最終長得像這樣：

`aaaaa.bbbbb.ccccc`

---

## 運作模式

* **登入與簽發**：使用者登入成功後，Server 會依使用者資訊製作 JWT 並回傳給 Client。
    
* **客戶端儲存**：前端自行決定將 JWT 存放於 LocalStorage、SessionStorage，或 Cookie。
    
* **Token 攜帶方式**：
    
    * 若 Token 可被 JS 讀取，前端會放在 `Authorization: Bearer <token>` Header。
        
    * 若存於 HttpOnly Cookie，則改由 Cookie 自動附帶，前端無法自行塞入 Header。
        
* **驗證簽名**：Server 收到 Token 後，以相同算法與密鑰計算 Signature，若結果一致且 Token 未過期，即視為合法。
    

---

## JWT 存放位置的取捨

JWT 常見的儲存方式主要有兩種：

### 方式一：存 LocalStorage（或可被 JS 讀取的 Cookie）

* 前端可自由存取 JWT
    
* 前端可手動塞入 Authorization Header
    
* 開發彈性高、最常用於 SPA 與行動 App
    
* **缺點：高度暴露於 XSS 風險**
    

### 方式二：存 HttpOnly Cookie（無法被 JS 讀取）

* 前端讀不到 Token → 不能放進 Authorization Header
    
* 攜帶完全依賴瀏覽器 Cookie（像 Session）
    
* 安全性較高（防止 JS 盜取）
    
* **需額外搭配 CSRF 防禦機制**
    

### 注意：

一旦選擇 HttpOnly Cookie，

**JWT 就無法再透過 Authorization: Bearer 傳遞**，

因為前端根本無法讀取 Token。

---

## 優點

* **無狀態 (Stateless)**
    
    Server 不需要儲存登入狀態。Token 中已包含所有必要資訊，只要每台 Server 使用相同的密鑰即可驗證。非常適合 Horizontal Scaling。
    
* **跨平台、跨網域彈性高（當 Token 可由前端讀取時）**
    
    若 Token 存於 LocalStorage / SessionStorage / 可讀 Cookie，前端可手動加到 Authorization Header。
    
    適用於 SPA、App、跨網域 API 等場景。
    
    （若採 HttpOnly Cookie，此優點即不適用。）
    

---

## 缺點

* **無法被撤銷 (Hard to Invalidate)**
    
    Token 一旦發出，在過期前都有效，Server 無法主動使其失效。
    
    解決方式：
    
    * 黑名單 (Blacklist)
        
    * 超短效 Access Token + Refresh Token
        
* **頻寬負擔較高**
    
    JWT 包含多段資訊，字串比 session\_id 大很多。
    
    存在 Cookie 或每次放 Header 都會增加傳輸成本。
    
* **Payload 可被解碼（非加密）**
    
    Base64URL 編碼任何人都能解開，因此不可放密碼、個資、敏感欄位。
    
* **若 Token 存在 LocalStorage / 可讀 Cookie，會受到 XSS 威脅**
    
    XSS 一旦發生，攻擊者能直接取得 Token。
    
    安全需求高的情境應改用 HttpOnly Cookie，但這會使前端無法再用 Authorization Header。
    
* **若 Token 存在 HttpOnly Cookie，將承受 CSRF 風險**
    
    因為 Cookie 會自動夾帶，仍需 SameSite、CSRF Token 等機制。
    
    並且會失去 JWT 作為純 API Token 的靈活性。
    

# 比較整理

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1765445660116/4816a3d4-47e4-4e51-b6d9-37986928fa1a.png align="center")

#
