Skip to main content

JWT vs. Cookie、Session

1. JWT(JSON Web Token)

  • 定義:JWT 是一種開放標準(RFC 7519),用於在客戶端和伺服器之間傳遞資訊的 token。它是一個由三部分組成的字串:Header(標頭)、Payload(負載)和 Signature(簽章),以.分隔,格式如下:

    Header.Payload.Signature;

    每個部分都經過 Base64 編碼,Payload 通常包含使用者資訊(如 ID、角色等),Signature 用來驗證 token 的完整性。

  • 運作方式

    1. 使用者登入後,伺服器生成一個 JWT,包含使用者的資訊並用私鑰簽署。

    2. 伺服器將 JWT 傳回給客戶端(通常放在 HTTP 回應的 body 或標頭)。

    3. 客戶端(通常是前端)將 JWT 儲存在本地(例如 localStorage 或 sessionStorage),之後每次發送 API 請求時,將 JWT 放在 HTTP 標頭(通常是 Authorization: Bearer <token>)中。

    4. 伺服器收到請求後,驗證 JWT 的簽章是否有效,並從 Payload 提取使用者資訊,無需查詢資料庫。

  • 程式碼範例(前端發送 JWT 請求):

    // 假設已經從伺服器取得 JWT 並儲存在 localStorage
    const token = localStorage.getItem("jwt_token");

    // 發送 API 請求時帶上 JWT
    fetch("https://api.example.com/protected", {
    method: "GET",
    headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
    },
    })
    .then((response) => response.json())
    .then((data) => console.log(data))
    .catch((error) => console.error("錯誤:", error));
  • 定義:Cookie Session 是一種基於伺服器端會話(Session)的身份驗證方式。伺服器在使用者登入後生成一個唯一的 Session ID,儲存在伺服器的資料庫或記憶體中,並將 Session ID 存入客戶端的 Cookie 中。

  • 運作方式

    1. 使用者登入後,伺服器生成一個唯一的 Session ID,並將其與使用者資訊儲存在伺服器端(例如 Redis 或資料庫)。

    2. 伺服器將 Session ID 寫入客戶端的 Cookie(通常設置為 HttpOnly 和 Secure)。

    3. 客戶端每次發送請求時,瀏覽器會自動將 Cookie 附在 HTTP 請求中。

    4. 伺服器根據 Cookie 中的 Session ID 查詢對應的使用者資訊,驗證身份。

  • 程式碼範例(前端無需特別處理 Cookie,瀏覽器自動處理):

    // 假設伺服器已設置好 Cookie,發送請求時瀏覽器會自動帶上
    fetch("https://api.example.com/protected", {
    method: "GET",
    credentials: "include", // 確保 Cookie 隨請求發送
    })
    .then((response) => response.json())
    .then((data) => console.log(data))
    .catch((error) => console.error("錯誤:", error));

以下是兩者的核心差異,分成幾個面向來比較:

面向JWTCookie Session
儲存方式客戶端儲存(通常在 localStorage 或 sessionStorage)。Session ID 儲存在客戶端的 Cookie,實際資料儲存在伺服器端。
資料內容JWT 包含使用者資訊(Payload),無需伺服器儲存狀態(無狀態)。Session ID 僅為一串隨機字串,實際使用者資訊儲存在伺服器(有狀態)。
驗證方式伺服器驗證簽章,無需查詢資料庫,適合分散式系統。伺服器需根據 Session ID 查詢資料庫或記憶體,確認使用者身份。
安全性依賴簽章保護,需妥善儲存避免 XSS 攻擊(例如竊取 localStorage)。Cookie 可設為 HttpOnly 和 Secure,可防 XSS,但需防 CSRF 攻擊。
效能無需伺服器儲存狀態,減少資料庫查詢,適合高流量系統。伺服器需儲存 Session 資料,隨著使用者增加,可能增加伺服器負擔。
有效期限通常有明確過期時間(由 Payload 的 exp 欄位設定)。Session 有效期限由伺服器控制,Cookie 可設置過期時間。
跨域支援需明確在請求標頭中加入 JWT,適合 API 驅動的應用程式。Cookie 可自動隨請求發送,適合傳統網頁應用,但跨域需設置 CORS。

三、優缺點比較

JWT 的優點

  1. 無狀態設計:伺服器不需儲存使用者狀態,適合分散式系統或微服務架構。

  2. 高效能:無需查詢資料庫,驗證速度快。

  3. 靈活:Payload 可包含自訂資訊(如角色、權限),方便前端或後端直接使用。

  4. 跨平台:適用於行動應用程式、單頁應用(SPA)等。

JWT 的缺點

  1. 安全性風險:若 JWT 被竊取(例如透過 XSS 攻擊),攻擊者可直接使用直到過期。

  2. 無法輕易撤銷:一旦發放,JWT 在有效期內無法主動失效,除非使用黑名單機制。

  3. Payload 容量限制:JWT 不宜包含過多資料,否則 token 會變得過長,增加傳輸負擔。

  1. 安全性較高:Cookie 可設為 HttpOnly,防止 XSS 攻擊;伺服器端可隨時終止 Session。

  2. 簡單易用:瀏覽器自動處理 Cookie,前端開發者無需額外管理。

  3. 靈活控制:伺服器可隨時更新或刪除 Session 資料。

  1. 伺服器負擔:需要儲存大量 Session 資料,隨著使用者增加,可能需要高效的資料庫或快取系統(如 Redis)。

  2. 跨域限制:跨域請求需額外設置 CORS 和 SameSite 屬性,否則可能無法正常工作。

  3. 不適合分散式系統:Session 資料通常集中儲存,擴展性不如 JWT。


  • 使用 JWT 的場景

    • 單頁應用(SPA)或行動應用程式,需與 RESTful API 互動。

    • 微服務架構或分散式系統,伺服器間無需共享 Session 資料。

    • 需要高效能且不依賴伺服器儲存狀態。

    • 範例:React 或 Vue 應用程式搭配無狀態 API。

  • 使用 Cookie Session 的場景

    • 傳統的伺服器渲染網頁應用(如 PHP、Ruby on Rails)。

    • 對安全性要求較高,且希望伺服器完全控制使用者狀態。

    • 需要頻繁更新或終止使用者 Session。

    • 範例:傳統網頁應用或需要 CSRF 防護的系統。


JWT 實例(前端 + 後端簡單驗證)

  • 前端(儲存和使用 JWT)

    // 登入後儲存 JWT
    async function login(username, password) {
    const response = await fetch("https://api.example.com/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username, password }),
    });
    const data = await response.json();
    if (data.token) {
    localStorage.setItem("jwt_token", data.token); // 儲存 JWT
    console.log("登入成功!");
    }
    }

    // 發送受保護的請求
    async function getProtectedData() {
    const token = localStorage.getItem("jwt_token");
    const response = await fetch("https://api.example.com/protected", {
    method: "GET",
    headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
    },
    });
    const data = await response.json();
    console.log("受保護資料:", data);
    }

    // 使用範例
    login("user", "password123");
    getProtectedData();
  • 後端(Node.js 簡單驗證 JWT,假設使用 jsonwebtoken 套件)

    const jwt = require("jsonwebtoken");
    const secret = "your-secret-key"; // 簽章用的私鑰

    // 登入生成 JWT
    app.post("/login", (req, res) => {
    const { username, password } = req.body;
    // 假設驗證成功
    const payload = { username, role: "user" };
    const token = jwt.sign(payload, secret, { expiresIn: "1h" });
    res.json({ token });
    });

    // 驗證 JWT 的受保護路由
    app.get("/protected", (req, res) => {
    const token = req.headers.authorization?.split(" ")[1];
    try {
    const decoded = jwt.verify(token, secret);
    res.json({ message: "成功訪問", user: decoded });
    } catch (error) {
    res.status(401).json({ message: "無效的 token" });
    }
    });
  • 前端(無需特別處理 Cookie)

    // 登入請求
    async function login(username, password) {
    const response = await fetch("https://api.example.com/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username, password }),
    credentials: "include", // 確保發送 Cookie
    });
    const data = await response.json();
    console.log("登入結果:", data);
    }

    // 取得受保護資料
    async function getProtectedData() {
    const response = await fetch("https://api.example.com/protected", {
    method: "GET",
    credentials: "include", // 自動帶上 Cookie
    });
    const data = await response.json();
    console.log("受保護資料:", data);
    }

    // 使用範例
    login("user", "password123");
    getProtectedData();
  • 後端(Node.js 使用 express-session 管理 Session)

    const express = require("express");
    const session = require("express-session");
    const app = express();

    app.use(express.json());
    app.use(
    session({
    secret: "your-session-secret",
    resave: false,
    saveUninitialized: false,
    cookie: { httpOnly: true, secure: true, maxAge: 3600000 }, // 1 小時
    })
    );

    // 登入生成 Session
    app.post("/login", (req, res) => {
    const { username, password } = req.body;
    // 假設驗證成功
    req.session.user = { username, role: "user" };
    res.json({ message: "登入成功" });
    });

    // 驗證 Session 的受保護路由
    app.get("/protected", (req, res) => {
    if (req.session.user) {
    res.json({ message: "成功訪問", user: req.session.user });
    } else {
    res.status(401).json({ message: "未登入" });
    }
    });

六、總結

  • JWT 適合無狀態、高效能、分散式系統,特別是 API 驅動的應用程式,但需注意 XSS 防護和 token 管理。

  • Cookie Session 適合傳統網頁應用,安全性較高且易於管理,但伺服器需承擔儲存負擔,且跨域處理較複雜。

  • 如果你是前端工程師,開發 SPA 或行動應用程式,JWT 是較常見的選擇,因為它與前端框架和 API 整合更方便;如果是傳統網頁應用,Cookie Session 可能更簡單且安全。