本文為 資安入門 系列文,第 1 篇
- OWASP Top 10:最新的 Web 安全風險與防範措施(2021 版)
- MD5 加密演算法是什麼?
- SHA-1 是什麼?為什麼不再安全?
- Session 入門:為什麼不該把用戶資料直接存在 Cookie? 👈所在位置
- 什麼是 Session Fixation(會話固定攻擊)?
- HTTP 與 HTTPS 的差別:新手完整指南
- 跨站請求偽造(CSRF)入門指南:攻擊原理、實例與防禦方法
- 跨站指令碼攻擊(XSS)入門指南:從原理到防禦的全解析
- SQL 注入攻擊全解析:從入門到防禦實戰指南
- 新手指南:深入了解 Content-Security-Policy (CSP) 與網站安全
- 從零理解 Same-Origin Policy:瀏覽器安全的第一道防線
- 跨來源資源共享(CORS)完整指南:打破瀏覽器的安全邊界
在上一篇文章中,我們介紹了 Cookie——一種讓瀏覽器幫網站「記住資料」的機制。
透過 Cookie,網站可以請瀏覽器存一些資訊,例如:
theme=dark
language=zh-TW這樣下次你來的時候,瀏覽器會自動把這些資料帶給網站,網站就知道你喜歡深色模式、偏好繁體中文。
這用來記「偏好設定」沒什麼問題。
但如果是記「你是誰」呢?
假設網站想記住「目前登入的是哪個用戶」,最直覺的做法可能是:
user_id=123
role=admin但這樣做有幾個問題:
- 使用者可以修改:Cookie 是存在使用者的電腦上的,使用者可以打開瀏覽器開發者工具,把
user_id=123改成user_id=456,假裝是另一個人 - 使用者可以看到:Cookie 是明文的,使用者看得到裡面存了什麼
- 容量有限:Cookie 只能存約 4KB,存不了太多資料
這顯然不太安全。那該怎麼辦呢?
第一個改進想法:把 Cookie 內容加密
既然問題是「使用者可以看到、可以修改」,那如果我們把 Cookie 的內容加密呢?
原本的 Cookie 是這樣:
user_id=123&role=admin加密後變成:
data=a8sYk2mX9z7bN3pQwE5rT1...(一串看不懂的亂碼)這樣使用者就看不懂內容了,也不知道怎麼改。
伺服器收到後,再用只有自己知道的金鑰解密,就能拿到原本的資料。
這個方法可行嗎?
可以,這種做法叫做「Encrypted Cookie」(加密 Cookie)。
但它有幾個問題:
問題一:Cookie 容量限制
Cookie 只能存約 4KB。加密後的資料通常會比原本更長,能存的東西更少。如果你想存購物車裡的 50 件商品,還是存不下。
問題二:加密不代表絕對安全
如果加密演算法不夠強,或是金鑰外洩,資料還是有可能被破解。而且使用者雖然看不懂內容,但他可以把整個加密字串複製給別人用。
問題三:每次請求都要傳送完整資料
每次瀏覽器發送請求,都要帶上這一大包加密資料,會增加傳輸量。
更好的做法:只存識別碼
與其把「加密後的資料」存在 Cookie,不如換個思路:
不如改成存一個「隨機產生的 ID」:
session_id=abc123xyz789這個 ID 本身沒有任何意義,只是一個識別碼。
那這個識別碼是做什麼用的?
它是用來「對應」伺服器上的資料的。
伺服器那邊會有一張表,記錄每個識別碼對應到哪些資料:
| Session ID | 對應的資料 |
|---|---|
| abc123xyz789 | user_id=123, role=admin |
| def456uvw012 | user_id=456, role=member |
當使用者發送請求時,伺服器會拿 Cookie 裡的 Session ID 去查這張表,找到對應的資料,就知道「這是誰」了。
這樣一來,就算使用者把 Cookie 裡的值亂改,伺服器也查不到對應的資料,就會當作「沒有登入」處理。
這個隨機產生的識別碼,就叫做 Session ID。而這整套做法,就叫做 Session 機制。
為什麼 Session 機制比較安全?
使用者只拿到 Session ID,沒有實際資料
使用者的 Cookie 裡只有:
session_id=abc123def456他看不到自己的 user_id、role 等資料,因為這些都存在伺服器端。
Session ID 是隨機產生的,無法被猜到
Session ID 通常是一串隨機產生的字串,例如:
abc123def456ghi789jkl012mno345pqr或是更長的:
a]veh%k!suji6j&rus&ijkhasdfq0^4f_sd!jk0重點是:它必須是隨機的、不可預測的。
一個好的 Session ID 應該像上面那樣,而不是這樣:
user_123如果 Session ID 是 user_123 這種格式,攻擊者只要把數字改成 user_124、user_125,就有可能猜到別人的 Session ID,進而假冒別人的身份。
只要 Session ID 夠隨機、夠長,別人就幾乎不可能猜到。
使用者無法修改伺服器端的資料
即使使用者能修改 Cookie 裡的 Session ID,他也只能改成另一個隨機字串。但這個字串在伺服器端沒有對應的資料,所以伺服器會當作「沒有登入」來處理。
他沒辦法說「我要把我的 role 從 member 改成 admin」,因為這個資料根本不在他那邊。
Session ID 是用來識別什麼?
Session ID 是一個識別碼,但它要「識別」的是什麼?
它識別的是一段「會話」(Session,又稱「工作階段」)。
什麼是會話?
就是你跟網站互動的一段期間。例如:
- 你下午 2:00 登入購物網站
- 逛了 30 分鐘,加了幾件商品到購物車
- 下午 2:30 結帳完成,登出
從登入到登出,這整段時間就是一個「Session(會話)」。
為什麼需要識別會話?
還記得我們在 Cookie 那篇說過的嗎?HTTP 是「無狀態」的,伺服器不會記得上一個請求是誰發的。
這代表什麼?
假設你在購物網站做了這些事:
- 第一個請求:登入
- 第二個請求:把商品 A 加入購物車
- 第三個請求:把商品 B 加入購物車
- 第四個請求:查看購物車
對伺服器來說,這四個請求「看起來」是四個獨立的請求,它不知道這些請求是同一個人發的。
這時候 Session ID 就派上用場了:
- 每個請求都帶上同一個 Session ID
- 伺服器看到 Session ID,就知道:「喔,這四個請求都來自同一段會話,是同一個人」
這樣伺服器才能把「商品 A」和「商品 B」都放進同一個購物車裡。
Session 機制是怎麼運作的?
當我們說「用 Session 機制」來處理登入狀態時,指的是這整套做法:
- 用戶登入成功,伺服器產生一個 Session ID
- 伺服器把用戶資料存起來,並記錄「這個 Session ID 對應到這些資料」
- 把 Session ID 透過 Cookie 傳給瀏覽器
- 之後用戶每次請求,瀏覽器自動帶上 Session ID
- 伺服器用 Session ID 查到對應的資料
讓我們用一個實際的例子來說明。假設你正在使用一個會員網站。
流程圖
sequenceDiagram
participant U as 使用者
participant B as 瀏覽器
participant S as 伺服器
Note over U,S: 第一次登入
U->>B: 輸入帳號密碼,點擊登入
B->>S: POST /login(帳號、密碼)
Note over S: 驗證成功,產生 Session ID,儲存對應資料
S->>B: 回應 + Set-Cookie: session_id=abc123
B->>U: 顯示「登入成功」
Note over B: 瀏覽器儲存 Cookie
Note over U,S: 後續請求(自動帶上 Session ID)
U->>B: 點擊「我的帳戶」
B->>S: GET /account + Cookie: session_id=abc123
Note over S: 用 Session ID 查到對應的資料
S->>B: 回應使用者的帳戶頁面
B->>U: 顯示帳戶資訊詳細步驟
步驟一:使用者登入
你在登入頁面輸入帳號和密碼,然後點擊「登入」按鈕。
瀏覽器會發送一個 HTTP Request 給伺服器:
POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
username=john&password=secret123步驟二:伺服器驗證身份,產生 Session ID
伺服器收到請求後,會去資料庫檢查帳號密碼是否正確。
如果驗證成功,伺服器會做兩件事:
- 產生 Session ID:產生一個隨機的識別碼
- 儲存對應資料:在伺服器端建立一個儲存空間,把這個 Session ID 對應到使用者的資料
伺服器端儲存的資料可能長這樣:
Session ID: abc123def456
對應的資料:
{
user_id: 123,
username: "john",
role: "member",
login_time: "2024-12-17 10:30:00"
}(關於伺服器把資料存在哪裡,後面會再詳細介紹。)
步驟三:伺服器把 Session ID 傳給瀏覽器
伺服器會在 HTTP Response 裡附上 Set-Cookie,把 Session ID 傳給瀏覽器:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Set-Cookie: session_id=abc123def456; HttpOnly; Secure; SameSite=Lax
<!DOCTYPE html>
<html>
<body>
<h1>登入成功!</h1>
</body>
</html>注意這裡的 Cookie 屬性:
HttpOnly:JavaScript 無法讀取,防止被惡意程式偷走Secure:只在 HTTPS 連線中傳送SameSite=Lax:防止跨站請求偽造攻擊
步驟四:瀏覽器儲存 Session ID
瀏覽器收到 Set-Cookie 後,會把 session_id=abc123def456 存起來。
從此以後,每次對這個網站發送請求,瀏覽器都會自動帶上這個 Cookie。
這就是 Cookie 在 Session 機制中的角色:負責把 Session ID 帶給伺服器。簡單說,Cookie 是「載具」,Session 機制是「怎麼用這個載具」的一種做法。
步驟五:後續請求,瀏覽器自動帶上 Session ID
當你點擊「我的帳戶」頁面,瀏覽器會發送:
GET /account HTTP/1.1
Host: www.example.com
Cookie: session_id=abc123def456步驟六:伺服器用 Session ID 查出對應資料
伺服器收到請求後,看到 Cookie 裡有 session_id=abc123def456。
伺服器用這個 Session ID 去查自己的儲存空間,找到對應的資料:
{
user_id: 123,
username: "john",
role: "member",
login_time: "2024-12-17 10:30:00"
}伺服器現在知道:「這是 john,他是普通會員」,然後就可以回傳對應的頁面內容給他。
伺服器把資料存在哪裡?
在 Session 機制中,伺服器需要儲存「Session ID 對應的資料」。這些資料可以存在不同的地方:
存在記憶體(Memory)
最簡單的做法,直接存在伺服器的記憶體裡。
優點:速度最快
缺點:伺服器重啟後,所有資料都會消失(使用者要重新登入)存在檔案(File)
把每筆資料存成一個檔案。
優點:伺服器重啟後,資料還在
缺點:讀寫檔案的速度比記憶體慢存在資料庫(Database)
把資料存在資料庫裡,例如 MySQL、PostgreSQL。
優點:資料持久化,可以查詢和管理
缺點:每次請求都要查資料庫,速度較慢存在快取系統(Redis、Memcached)
Redis 是一種高速的「記憶體資料庫」,很多網站會用它來存這些資料。
優點:速度快,而且可以跨多台伺服器共享
缺點:需要額外架設 Redis 伺服器會話的過期機制
伺服器不會永遠保存 Session ID 和對應的資料,它們有「過期時間」。
為什麼需要過期?
安全考量
如果資料永遠不過期,萬一 Session ID 被偷走,壞人就能永遠假冒你。
舉個例子:你在公用電腦登入了網路銀行,忘記登出就離開了。如果資料永遠不過期,下一個使用這台電腦的人就能一直用你的身份操作帳戶。
但如果伺服器設定 15 分鐘後過期,就算你忘記登出,15 分鐘後這筆記錄就會被刪除,Session ID 就失效了。
節省資源
伺服器需要為每個 Session ID 儲存對應的資料。如果這些資料永遠不過期,會越積越多,最後把伺服器的儲存空間塞滿。
過期機制讓伺服器可以定期清理不再使用的資料。
常見的過期方式
絕對過期時間
伺服器會記錄每個 Session ID 是什麼時候建立的。經過固定的時間後,伺服器就會把這整筆記錄刪除——包含 Session ID 和它對應的資料。
Session ID: abc123def456
建立時間:10:00
對應的資料:{ user_id: 123, username: "john", ... }
過期時間設定為 1 小時
→ 11:00 時,不管使用者有沒有在操作,伺服器都會刪除這整筆記錄
→ 之後使用者再帶著這個 Session ID 來,伺服器會查不到資料
→ 使用者被當作「沒有登入」適合的場景:安全性要求高的網站,例如網路銀行。就算你一直在操作,1 小時後還是要重新登入。
閒置過期時間(Idle Timeout)
伺服器會記錄每個 Session ID「最後一次被使用」的時間。每次使用者發送請求,伺服器就會更新這個時間。如果超過一段時間沒有新的請求,伺服器就會把這整筆記錄刪除。
Session ID: abc123def456
閒置過期時間設定為 30 分鐘
使用者 10:00 登入 → 最後活動時間:10:00
使用者 10:15 點了某個頁面 → 最後活動時間更新為:10:15
使用者 10:20 點了某個頁面 → 最後活動時間更新為:10:20
使用者 10:20 之後就沒動作了
→ 10:50 時(閒置超過 30 分鐘),伺服器刪除這整筆記錄
→ 之後使用者再帶著這個 Session ID 來,伺服器會查不到資料
→ 使用者被當作「沒有登入」適合的場景:一般網站,例如購物網站、社群網站。只要你持續在使用,就不會被強制登出。
實務上:兩種方式常常一起使用
很多網站會同時設定兩種過期時間:
絕對過期時間:8 小時
閒置過期時間:30 分鐘這表示:
- 如果你一直在使用,最多 8 小時後會被強制登出
- 如果你中途閒置超過 30 分鐘,也會被登出
「記住我」功能是怎麼運作的?
你應該在很多網站看過登入時的「記住我」選項。勾選後,就算關掉瀏覽器,下次打開還是維持登入狀態。
這是怎麼做到的?
要維持登入狀態,兩邊都要配合:
- 瀏覽器端:Cookie 裡的 Session ID 不能消失
- 伺服器端:Session ID 對應的資料不能被刪除
所以「記住我」功能需要同時調整兩邊的過期時間。
沒勾選「記住我」
伺服器回傳的 Set-Cookie 不設定 Max-Age 或 Expires:
Set-Cookie: session_id=abc123def456; HttpOnly; Secure這種 Cookie 叫做「Session Cookie」,瀏覽器關閉後就會自動刪除。
同時,伺服器端的過期時間也設定得比較短,例如 30 分鐘閒置就刪除。
勾選「記住我」
伺服器回傳的 Set-Cookie 會設定較長的 Max-Age:
Set-Cookie: session_id=abc123def456; HttpOnly; Secure; Max-Age=2592000Max-Age=2592000 表示這個 Cookie 會保留 30 天(60 × 60 × 24 × 30 秒)。
同時,伺服器端也會把這筆記錄的過期時間設定為 30 天。
這樣兩邊都設定了 30 天,就算關掉瀏覽器,下次打開時:
- 瀏覽器的 Cookie 還在 → 會帶著 Session ID 發送請求
- 伺服器的資料也還在 → 能查到對應的使用者資料
使用者就能維持登入狀態。
實際範例:一個完整的登入流程
讓我們把所有東西串起來,看一個完整的登入流程。
使用者發送登入請求
POST /login HTTP/1.1
Host: www.shop.com
Content-Type: application/x-www-form-urlencoded
username=alice&password=mypassword伺服器驗證成功,產生 Session ID 並回應
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: session_id=xyz789abc; HttpOnly; Secure; SameSite=Lax; Max-Age=3600
{
"status": "success",
"message": "登入成功"
}伺服器內部儲存的資料:
Session ID: xyz789abc
對應的資料:
{
user_id: 456,
username: "alice",
cart: ["item_001", "item_002"],
login_time: "2024-12-17 14:00:00"
}使用者瀏覽商品頁面
GET /products HTTP/1.1
Host: www.shop.com
Cookie: session_id=xyz789abc使用者加入商品到購物車
POST /cart/add HTTP/1.1
Host: www.shop.com
Cookie: session_id=xyz789abc
Content-Type: application/json
{
"product_id": "item_003"
}伺服器收到後,用 Session ID 找到 alice 的資料,更新購物車:
Session ID: xyz789abc
對應的資料:
{
user_id: 456,
username: "alice",
cart: ["item_001", "item_002", "item_003"], // 新增了 item_003
login_time: "2024-12-17 14:00:00"
}5. 使用者查看購物車
GET /cart HTTP/1.1
Host: www.shop.com
Cookie: session_id=xyz789abc伺服器回應:
HTTP/1.1 200 OK
Content-Type: application/json
{
"cart": ["item_001", "item_002", "item_003"],
"total": 3
}小結
現在你知道了:
- 為什麼需要 Session 機制:直接用 Cookie 存資料不安全,使用者可以看到也可以修改
- Session ID 是什麼:一個隨機產生的識別碼,用來識別一段會話
- Session(會話)是什麼:使用者與網站互動的一段期間
- Session 機制怎麼運作:Cookie 負責傳遞 Session ID,伺服器用它來查找對應的資料
- 資料的儲存方式:記憶體、檔案、資料庫、Redis 等
- Session 機制的挑戰:多台伺服器時的資料同步問題
Session 機制是 Web 開發中非常重要的概念,幾乎所有需要「登入」功能的網站都會用到它。理解它的運作方式,能幫助你更好地理解 Web 應用程式的架構。
但 Session 機制也有它的限制——特別是在大規模、分散式的系統中,伺服器需要儲存每個使用者的資料,這會帶來擴展性的挑戰。
下一篇文章,我們會介紹另一種做法:不在伺服器端儲存狀態,而是把資料放在 Token 裡面。