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
- HttpOnly (防禦 XSS 的關鍵)
意義:設定了這個屬性的 Cookie,無法透過 JavaScript (如
document.cookie) 讀取。為什麼重要:這是防禦 XSS (跨站腳本攻擊) 最重要的設定。假設駭客在你的網站留言版植入了一段惡意 JS 代碼來竊取 Cookie,如果 Session ID 有開啟
HttpOnly,駭客的程式碼就讀不到該 Cookie,有效降低帳號被盜用的風險。結論:敏感資訊 (如 Session ID) 務必開啟此選項。
- Secure (只許加密傳輸)
意義:設定此屬性後,Cookie 只能透過 HTTPS 協定傳輸。如果是普通的 HTTP (明文) 請求,瀏覽器就不會帶上這個 Cookie。
為什麼重要:防止攻擊者在網路節點中進行監聽 (Man-in-the-Middle attack) 攔截到明文的 Cookie。
結論:在正式環境 (Production) 中,必須開啟。
- Domain & Path (限制作用範圍)
Domain:指定 Cookie 在哪個網域下有效。
如果不設定,預設是當前 Domain (不含子網域)。
如果設定
Domain=example.com,則api.example.com或www.example.com等子網域都能讀取到此 Cookie。
Path:指定 URL 路徑下有效。
通常設定為
/(根目錄),代表整站都通用。若設定
/admin,則只有在進入/admin開頭的網址時,瀏覽器才會帶上此 Cookie。
⚠️ 注意:Domain 不能隨便設! 設定 Cookie 的 Domain 屬性時,你只能設定「當前網域」或「當前網域的父網域」。 例如 api.example.com 可以設 Domain=.example.com,但不能設 Domain=google.com。 如果你的前後端網域完全不同 (例如前後端分離部署在不同平台),建議改用 JWT,會比硬要設定 Cookie 來的省事許多。
- 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 由三個部分組成:
Header(演算法,例如:HS256)
Payload(資料內容,例如:user_id、nick_name…)
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 的靈活性。
比較整理

#

