Session 入門:為什麼不該把用戶資料直接存在 Cookie?

最後更新:2025年12月17日
架構

上一篇文章中,我們介紹了 Cookie——一種讓瀏覽器幫網站「記住資料」的機制。

透過 Cookie,網站可以請瀏覽器存一些資訊,例如:

theme=dark
language=zh-TW

這樣下次你來的時候,瀏覽器會自動把這些資料帶給網站,網站就知道你喜歡深色模式、偏好繁體中文。

這用來記「偏好設定」沒什麼問題。

但如果是記「你是誰」呢?

假設網站想記住「目前登入的是哪個用戶」,最直覺的做法可能是:

user_id=123
role=admin

但這樣做有幾個問題:

  1. 使用者可以修改:Cookie 是存在使用者的電腦上的,使用者可以打開瀏覽器開發者工具,把 user_id=123 改成 user_id=456,假裝是另一個人
  2. 使用者可以看到:Cookie 是明文的,使用者看得到裡面存了什麼
  3. 容量有限:Cookie 只能存約 4KB,存不了太多資料

這顯然不太安全。那該怎麼辦呢?

既然問題是「使用者可以看到、可以修改」,那如果我們把 Cookie 的內容加密呢?

原本的 Cookie 是這樣:

user_id=123&role=admin

加密後變成:

data=a8sYk2mX9z7bN3pQwE5rT1...(一串看不懂的亂碼)

這樣使用者就看不懂內容了,也不知道怎麼改。

伺服器收到後,再用只有自己知道的金鑰解密,就能拿到原本的資料。

這個方法可行嗎?

可以,這種做法叫做「Encrypted Cookie」(加密 Cookie)。

但它有幾個問題:

問題一:Cookie 容量限制

Cookie 只能存約 4KB。加密後的資料通常會比原本更長,能存的東西更少。如果你想存購物車裡的 50 件商品,還是存不下。

問題二:加密不代表絕對安全

如果加密演算法不夠強,或是金鑰外洩,資料還是有可能被破解。而且使用者雖然看不懂內容,但他可以把整個加密字串複製給別人用。

問題三:每次請求都要傳送完整資料

每次瀏覽器發送請求,都要帶上這一大包加密資料,會增加傳輸量。

更好的做法:只存識別碼

與其把「加密後的資料」存在 Cookie,不如換個思路:

不如改成存一個「隨機產生的 ID」:

session_id=abc123xyz789

這個 ID 本身沒有任何意義,只是一個識別碼。

那這個識別碼是做什麼用的?

它是用來「對應」伺服器上的資料的。

伺服器那邊會有一張表,記錄每個識別碼對應到哪些資料:

對應的資料user_id=123, role=admin
對應的資料user_id=456, role=member

當使用者發送請求時,伺服器會拿 Cookie 裡的 Session ID 去查這張表,找到對應的資料,就知道「這是誰」了。

這樣一來,就算使用者把 Cookie 裡的值亂改,伺服器也查不到對應的資料,就會當作「沒有登入」處理。

這個隨機產生的識別碼,就叫做 Session ID。而這整套做法,就叫做 Session 機制

為什麼 Session 機制比較安全?

使用者只拿到 Session ID,沒有實際資料

使用者的 Cookie 裡只有:

session_id=abc123def456

他看不到自己的 user_idrole 等資料,因為這些都存在伺服器端。

Session ID 是隨機產生的,無法被猜到

Session ID 通常是一串隨機產生的字串,例如:

abc123def456ghi789jkl012mno345pqr

或是更長的:

a]veh%k!suji6j&rus&ijkhasdfq0^4f_sd!jk0

重點是:它必須是隨機的、不可預測的

一個好的 Session ID 應該像上面那樣,而不是這樣:

user_123

如果 Session ID 是 user_123 這種格式,攻擊者只要把數字改成 user_124user_125,就有可能猜到別人的 Session ID,進而假冒別人的身份。

只要 Session ID 夠隨機、夠長,別人就幾乎不可能猜到。

使用者無法修改伺服器端的資料

即使使用者能修改 Cookie 裡的 Session ID,他也只能改成另一個隨機字串。但這個字串在伺服器端沒有對應的資料,所以伺服器會當作「沒有登入」來處理。

他沒辦法說「我要把我的 role 從 member 改成 admin」,因為這個資料根本不在他那邊。

Session ID 是用來識別什麼?

Session ID 是一個識別碼,但它要「識別」的是什麼?

它識別的是一段「會話」(Session,又稱「工作階段」)。

什麼是會話?

就是你跟網站互動的一段期間。例如:

  • 你下午 2:00 登入購物網站
  • 逛了 30 分鐘,加了幾件商品到購物車
  • 下午 2:30 結帳完成,登出

從登入到登出,這整段時間就是一個「Session(會話)」。

為什麼需要識別會話?

還記得我們在 Cookie 那篇說過的嗎?HTTP 是「無狀態」的,伺服器不會記得上一個請求是誰發的。

這代表什麼?

假設你在購物網站做了這些事:

  1. 第一個請求:登入
  2. 第二個請求:把商品 A 加入購物車
  3. 第三個請求:把商品 B 加入購物車
  4. 第四個請求:查看購物車

對伺服器來說,這四個請求「看起來」是四個獨立的請求,它不知道這些請求是同一個人發的。

這時候 Session ID 就派上用場了:

  • 每個請求都帶上同一個 Session ID
  • 伺服器看到 Session ID,就知道:「喔,這四個請求都來自同一段會話,是同一個人」

這樣伺服器才能把「商品 A」和「商品 B」都放進同一個購物車裡。

Session 機制是怎麼運作的?

當我們說「用 Session 機制」來處理登入狀態時,指的是這整套做法:

  1. 用戶登入成功,伺服器產生一個 Session ID
  2. 伺服器把用戶資料存起來,並記錄「這個 Session ID 對應到這些資料」
  3. 把 Session ID 透過 Cookie 傳給瀏覽器
  4. 之後用戶每次請求,瀏覽器自動帶上 Session ID
  5. 伺服器用 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

伺服器收到請求後,會去資料庫檢查帳號密碼是否正確。

如果驗證成功,伺服器會做兩件事:

  1. 產生 Session ID:產生一個隨機的識別碼
  2. 儲存對應資料:在伺服器端建立一個儲存空間,把這個 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-AgeExpires

Set-Cookie: session_id=abc123def456; HttpOnly; Secure

這種 Cookie 叫做「Session Cookie」,瀏覽器關閉後就會自動刪除。

同時,伺服器端的過期時間也設定得比較短,例如 30 分鐘閒置就刪除。

勾選「記住我」

伺服器回傳的 Set-Cookie 會設定較長的 Max-Age

Set-Cookie: session_id=abc123def456; HttpOnly; Secure; Max-Age=2592000

Max-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
}

小結

現在你知道了:

  1. 為什麼需要 Session 機制:直接用 Cookie 存資料不安全,使用者可以看到也可以修改
  2. Session ID 是什麼:一個隨機產生的識別碼,用來識別一段會話
  3. Session(會話)是什麼:使用者與網站互動的一段期間
  4. Session 機制怎麼運作:Cookie 負責傳遞 Session ID,伺服器用它來查找對應的資料
  5. 資料的儲存方式:記憶體、檔案、資料庫、Redis 等
  6. Session 機制的挑戰:多台伺服器時的資料同步問題

Session 機制是 Web 開發中非常重要的概念,幾乎所有需要「登入」功能的網站都會用到它。理解它的運作方式,能幫助你更好地理解 Web 應用程式的架構。

但 Session 機制也有它的限制——特別是在大規模、分散式的系統中,伺服器需要儲存每個使用者的資料,這會帶來擴展性的挑戰。

下一篇文章,我們會介紹另一種做法:不在伺服器端儲存狀態,而是把資料放在 Token 裡面。