Skip to main content

Session 與 Cookie 的基本概念

咖啡店的點單問題

假設你是一家咖啡店的店員,名叫小明,每天有許多顧客來點咖啡。咖啡店的點單系統有一個問題:每次顧客點完飲料後,系統完全不記得他們是誰、點過什麼,也不知道他們是否已經付錢。這種「不記得顧客資訊」的狀況,就像是 HTTP 協定的無狀態(Stateless)特性

有一天,顧客小華來點了一杯拿鐵,並說:「記得我每次都要加兩匙糖!」但因為系統無狀態,小華下次再來時,小明又得重新問:「請問您要什麼?加糖嗎?」小華覺得很麻煩,於是小明想出了一個方法:

  1. 用小紙條(Cookie):小明給小華一張小紙條,上面寫著「小華,喜歡拿鐵,加兩匙糖」。每次小華來店裡,小明請他出示這張紙條,這樣就不用重新問了。

  2. 用店內記錄本(Session):小明在店裡放了一本記錄本,記下「顧客編號 001:小華,喜歡拿鐵,加兩匙糖」。小明給小華一張只有編號「001」的小紙條,小華每次來只要出示編號,小明就能查記錄本,找到小華的偏好。

這兩個方法分別對應了 CookieSession 的概念!接下來,我會詳細說明它們的技術背景,並用程式碼範例讓你更清楚。


一、什麼是無狀態的 HTTP?

HTTP(HyperText Transfer Protocol)是網頁瀏覽器和伺服器之間溝通的協定。它的「無狀態」(Stateless)特性是指:每次 HTTP 請求(Request)都是獨立的,伺服器不會自動記住之前的請求。就像故事中,小明每次都得重新問小華的點單,因為系統不記得之前的對話。

為什麼無狀態有問題?

假設你在一個購物網站上:

  1. 你登入帳號,伺服器確認你是「小華」。

  2. 你把一件衣服加入購物車。

  3. 你點擊「結帳」,但因為 HTTP 無狀態,伺服器忘記你是誰,也不知道你的購物車裡有什麼!

這顯然很麻煩,因為使用者希望網站能「記住」他們的狀態(像是登入資訊、購物車內容)。這就是為什麼我們需要 CookieSession


Cookie 是一小段儲存在使用者瀏覽器中的文字資料,由伺服器發送,用來記錄使用者的資訊。就像故事中的「小紙條」,Cookie 會跟著每次 HTTP 請求一起送回伺服器,讓伺服器知道「你是誰」。

  1. 使用者發送 HTTP 請求(例如登入)。

  2. 伺服器回應時,會在 HTTP 標頭(Header)中加入 Set-Cookie,告訴瀏覽器儲存一些資料。

  3. 瀏覽器把 Cookie 存在本地,以後每次請求同一個網站時,會自動把 Cookie 附在 HTTP 標頭中送回伺服器。

  • 記錄登入狀態(例如使用者 ID)。

  • 儲存使用者的偏好(例如網站主題是深色還是淺色)。

  • 追蹤使用者行為(例如廣告追蹤)。

假設我們想用 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")); // 輸出:小華

程式碼說明

  1. setCookie:設定一個 Cookie,包含名稱(name)、值(value)和有效天數(days)。

  2. getCookie:從 document.cookie 讀取指定名稱的 Cookie 值。

  3. document.cookie 是瀏覽器提供的 API,用來操作 Cookie,但它是一個字串,需自行解析。

  • 每個 Cookie 的大小限制在 4KB 左右。

  • 每個網域的 Cookie 數量有限(通常 20~50 個,依瀏覽器而定)。

  • 安全性問題:Cookie 可能被竊取(例如透過 XSS 攻擊),所以重要資料應加密。


三、什麼是 Session?

Session(工作階段) 是伺服器端用來儲存使用者狀態的機制。就像故事中的「記錄本」,伺服器會把使用者的資料存在伺服器上,並給使用者一個唯一的「編號」(Session ID),存在 Cookie 中。每次使用者發送請求時,瀏覽器會把 Session ID 送回,伺服器根據這個 ID 查詢對應的資料。

Session 的運作方式

  1. 使用者發送請求(例如登入)。

  2. 伺服器建立一個 Session,儲存使用者的資料(例如使用者 ID、購物車內容),並產生一個唯一的 Session ID。

  3. 伺服器透過 Set-Cookie 將 Session ID 傳給瀏覽器,儲存在 Cookie 中。

  4. 以後每次請求,瀏覽器會自動帶上這個 Session ID,伺服器就能找到對應的 Session 資料。

  • 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>

程式碼說明

  1. 後端使用 express-session 套件來管理 Session,自動產生 Session ID 並儲存在 Cookie 中。

  2. 前端送出登入請求後,後端會在 Session 中儲存使用者名稱,並自動設定 Cookie 包含 Session ID。

  3. 當前端請求 /profile 時,瀏覽器會帶上 Cookie 中的 Session ID,後端根據這個 ID 找到對應的 Session 資料。

Session 的優點與限制

  • 優點:資料儲存在伺服器,安全性較高,適合儲存敏感資訊(例如使用者 ID)。

  • 限制

    • 伺服器需要額外儲存空間(記憶體或資料庫)。

    • 如果伺服器重啟,記憶體中的 Session 可能會丟失(除非使用資料庫持久化)。

    • 在分散式系統中,需要額外設定(如 Redis)來同步 Session。


四、Cookie 和 Session 的比較

特性CookieSession
儲存位置瀏覽器(客戶端)伺服器(Session ID 存在 Cookie)
資料大小限制在 4KB取決於伺服器儲存空間
安全性較低,容易被竊取較高,敏感資料儲在伺服器
用途儲存簡單資料(偏好、追蹤 ID)儲存複雜狀態(登入、購物車)
有效期可設定過期時間通常與瀏覽器關閉或設定時間結束

五、實務中的應用建議

  1. 使用 Cookie

    • 儲存非敏感資料,例如使用者的語言偏好、主題設定。

    • 設定 HttpOnly 和 Secure 屬性,防止 XSS 攻擊和確保 HTTPS 傳輸。

    • 範例:Set-Cookie: theme=dark; HttpOnly; Secure

  2. 使用 Session

    • 儲存敏感資料,例如登入狀態、購物車內容。

    • 搭配資料庫或 Redis 來持久化 Session 資料。

    • 確保 Session ID 是隨機且難以猜測的。

  3. 前端注意事項

    • 使用 fetch 或 axios 發送請求時,確保 credentials: "include",讓瀏覽器自動帶上 Cookie。

    • 範例:

      fetch("http://localhost:3000/profile", {
      credentials: "include", // 自動帶上 Cookie
      });

六、總結

回到咖啡店的故事:

  • 無狀態的 HTTP 就像小明每次都忘記顧客的點單,沒有效率。

  • Cookie 像小紙條,記錄簡單的資訊,直接存在顧客(瀏覽器)身上。

  • Session 像店內的記錄本,記錄詳細資料,只給顧客一個編號(Session ID)。