Session 與 Cookie 的基本概念
咖啡店的點單問題
假設你是一家咖啡店的店員,名叫小明,每天有許多顧客來點咖啡。咖啡店的點單系統有一個問題:每次顧客點完飲料後,系統完全不記得他們是誰、點過什麼,也不知道他們是否已經付錢。這種「不記得顧客資訊」的狀況,就像是 HTTP 協定的無狀態(Stateless)特性。
有一天,顧客小華來點了一杯拿鐵,並說:「記得我每次都要加兩匙糖!」但因為系統無狀態,小華下次再來時,小明又得重新問:「請問您要什麼?加糖嗎?」小華覺得很麻煩,於是小明想出了一個方法:
-
用小紙條(Cookie):小明給小華一張小紙條,上面寫著「小華,喜歡拿鐵,加兩匙糖」。每次小華來店裡,小明請他出示這張紙條,這樣就不用重新問了。
-
用店內記錄本(Session):小明在店裡放了一本記錄本,記下「顧客編號 001:小華,喜歡拿鐵,加兩匙糖」。小明給小華一張只有編號「001」的小紙條,小華每次來只要出示編號,小明就能查記錄本,找到小華的偏好。
這兩個方法分別對應了 Cookie 和 Session 的概念!接下來,我會詳細說明它們的技術背景,並用程式碼範例讓你更清楚。
一、什麼是無狀態的 HTTP?
HTTP(HyperText Transfer Protocol)是網頁瀏覽器和伺服器之間溝通的協定。它的「無狀態」(Stateless)特性是指:每次 HTTP 請求(Request)都是獨立的,伺服器不會自動記住之前的請求。就像故事中,小明每次都得重新問小華的點單,因為系統不記得之前的對話。
為什麼無狀態有問題?
假設你在一個購物網站上:
-
你登入帳號,伺服器確認你是「小華」。
-
你把一件衣服加入購物車。
-
你點擊「結帳」,但因為 HTTP 無狀態,伺服器忘記你是誰,也不知道你的購物車裡有什麼!
這顯然很麻煩,因為使用者希望網站能「記住」他們的狀態(像是登入資訊、購物車內容)。這就是為什麼我們需要 Cookie 和 Session。
二、什麼是 Cookie?
Cookie 是一小段儲存在使用者瀏覽器中的文字資料,由伺服器發送,用來記錄使用者的資訊。就像故事中的「小紙條」,Cookie 會跟著每次 HTTP 請求一起送回伺服器,讓伺服器知道「你是誰」。
Cookie 的運作方式
-
使用者發送 HTTP 請求(例如登入)。
-
伺服器回應時,會在 HTTP 標頭(Header)中加入 Set-Cookie,告訴瀏覽器儲存一些資料。
-
瀏覽器把 Cookie 存在本地,以後每次請求同一個網站時,會自動把 Cookie 附在 HTTP 標頭中送回伺服器。
Cookie 的用途
-
記錄登入狀態(例如使用者 ID)。
-
儲存使用者的偏好(例如網站主題是深色還是淺色)。
-
追蹤使用者行為(例如廣告追蹤)。
JavaScript 操作 Cookie 的範例
假設我們想用 JavaScript 來設定和讀取 Cookie,記錄使用者的名字:
// 設定 Cookie
function setCookie(name, value, days) {
let expires = "";
if (days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); // 設定過期時間(天數)
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + value + expires + "; path=/";
}
// 讀取 Cookie
function getCookie(name) {
const nameEQ = name + "=";
const cookies = document.cookie.split(";"); // 分割所有 Cookie
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.indexOf(nameEQ) === 0) {
return cookie.substring(nameEQ.length, cookie.length);
}
}
return null;
}
// 使用範例
setCookie("username", "小華", 7); // 儲存 username 為「小華」,有效期 7 天
console.log(getCookie("username")); // 輸出:小華
程式碼說明:
-
setCookie:設定一個 Cookie,包含名稱(name)、值(value)和有效天數(days)。
-
getCookie:從 document.cookie 讀取指定名稱的 Cookie 值。
-
document.cookie 是瀏覽器提供的 API,用來操作 Cookie,但它是一個字串,需自行解析。
Cookie 的限制
-
每個 Cookie 的大小限制在 4KB 左右。
-
每個網域的 Cookie 數量有限(通常 20~50 個,依瀏覽器而定)。
-
安全性問題:Cookie 可能被竊取(例如透過 XSS 攻擊),所以重要資料應加密。
三、什麼是 Session?
Session(工作階段) 是伺服器端用來儲存使用者狀態的機制。就像故事中的「記錄本」,伺服器會把使用者的資料存在伺服器上,並給使用者一個唯一的「編號」(Session ID),存在 Cookie 中。每次使用者發送請求時,瀏覽器會把 Session ID 送回,伺服器根據這個 ID 查詢對應的資料。
Session 的運作方式
-
使用者發送請求(例如登入)。
-
伺服器建立一個 Session,儲存使用者的資料(例如使用者 ID、購物車內容),並產生一個唯一的 Session ID。
-
伺服器透過 Set-Cookie 將 Session ID 傳給瀏覽器,儲存在 Cookie 中。
-
以後每次請求,瀏覽器會自動帶上這個 Session ID,伺服器就能找到對應的 Session 資料。
Session 和 Cookie 的關係
-
Cookie 是載體:Session ID 通常儲存在 Cookie 中,作為伺服器和瀏覽器之間的橋樑。
-
Session 是資料庫:實際的資料(例如使用者資訊)存在伺服器端的記憶體、資料庫或檔案中。
JavaScript 與後端的 Session 範例
假設你用 Node.js 和 Express 來實作一個簡單的 Session 機制:
// 後端程式碼(Node.js + Express)
const express = require("express");
const session = require("express-session"); // 需要安裝 express-session
const app = express();
// 設定 Session
app.use(
session({
secret: "my-secret-key", // 用來簽署 Session ID 的密鑰
resave: false,
saveUninitialized: false,
cookie: { maxAge: 24 * 60 * 60 * 1000 }, // Cookie 有效期 1 天
})
);
// 登入路由
app.post("/login", (req, res) => {
const username = req.body.username; // 假設從前端收到 username
req.session.username = username; // 將 username 存進 Session
res.send("登入成功!");
});
// 檢查使用者狀態
app.get("/profile", (req, res) => {
if (req.session.username) {
res.send(`歡迎回來,${req.session.username}!`);
} else {
res.send("請先登入!");
}
});
app.listen(3000, () => console.log("伺服器運行在 http://localhost:3000"));
前端程式碼(簡單的登入表單):
<!DOCTYPE html>
<html>
<head>
<title>登入</title>
</head>
<body>
<form id="loginForm">
<input type="text" id="username" placeholder="輸入使用者名稱">
<button type="submit">登入</button>
</form>
<button onclick="checkProfile()">查看個人資料</button>
<script>
// 送出登入請求
document.getElementById("loginForm").addEventListener("submit", async (e) => {
e.preventDefault();
const username = document.getElementById("username").value;
const response = await fetch("http://localhost:3000/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username }),
});
const result = await response.text();
alert(result); // 顯示「登入成功!」
});
// 檢查個人資料
async function checkProfile() {
const response = await fetch("http://localhost:3000/profile");
const result = await response.text();
alert(result); // 顯示「歡迎回來,小華!」或「請先登入!」
}
</script>
</body>
</html>
程式碼說明:
-
後端使用 express-session 套件來管理 Session,自動產生 Session ID 並儲存在 Cookie 中。
-
前端送出登入請求後,後端會在 Session 中儲存使用者名稱,並自動設定 Cookie 包含 Session ID。
-
當前端請求 /profile 時,瀏覽器會帶上 Cookie 中的 Session ID,後端根據這個 ID 找到對應的 Session 資料。
Session 的優點與限制
-
優點:資料儲存在伺服器,安全性較高,適合儲存敏感資訊(例如使用者 ID)。
-
限制:
-
伺服器需要額外儲存空間(記憶體或資料庫)。
-
如果伺服器重啟,記憶體中的 Session 可能會丟失(除非使用資料庫持久化)。
-
在分散式系統中,需要額外設定(如 Redis)來同步 Session。
-
四、Cookie 和 Session 的比較
| 特性 | Cookie | Session |
|---|---|---|
| 儲存位置 | 瀏覽器(客戶端) | 伺服器(Session ID 存在 Cookie) |
| 資料大小 | 限制在 4KB | 取決於伺服器儲存空間 |
| 安全性 | 較低,容易被竊取 | 較高,敏感資料儲在伺服器 |
| 用途 | 儲存簡單資料(偏好、追蹤 ID) | 儲存複雜狀態(登入、購物車) |
| 有效期 | 可設定過期時間 | 通常與瀏覽器關閉或設定時間結束 |
五、實務中的應用建議
-
使用 Cookie:
-
儲存非敏感資料,例如使用者的語言偏好、主題設定。
-
設定 HttpOnly 和 Secure 屬性,防止 XSS 攻擊和確保 HTTPS 傳輸。
-
範例:Set-Cookie: theme=dark; HttpOnly; Secure
-
-
使用 Session:
-
儲存敏感資料,例如登入狀態、購物車內容。
-
搭配資料庫或 Redis 來持久化 Session 資料。
-
確保 Session ID 是隨機且難以猜測的。
-
-
前端注意事項:
-
使用 fetch 或 axios 發送請求時,確保 credentials: "include",讓瀏覽器自動帶上 Cookie。
-
範例:
fetch("http://localhost:3000/profile", {
credentials: "include", // 自動帶上 Cookie
});
-
六、總結
回到咖啡店的故事:
-
無狀態的 HTTP 就像小明每次都忘記顧客的點單,沒有效率。
-
Cookie 像小紙條,記錄簡單的資訊,直接存在顧客(瀏覽器)身上。
-
Session 像店內的記錄本,記錄詳細資料,只給顧客一個編號(Session ID)。