Danh sách bài viết

Bài 76: Vì Sao Session Store với Redis

Bài mở đầu Module 7 giải thích tại sao Redis được dùng làm session store thay vì in-memory app, SQL database hay NoSQL document store. Nội dung gồm: session là gì và dữ liệu nào nên lưu; phân tích từng lựa chọn lưu trữ với số liệu cụ thể; workflow đầy đủ từ login đến authenticated request; code Python FastAPI với sliding window TTL; so sánh JSON string với Hash; TTL strategies; persistence với AOF; security và các anti-pattern phổ biến nhất.

01/06/2026
14 phút đọc
0 lượt xem
1

Mục Tiêu Bài Học

  • Hiểu session là gì và dữ liệu nào thường được lưu trong session.
  • So sánh cụ thể 4 lựa chọn lưu session: in-memory app, SQL DB, NoSQL document store, Redis — với lý do kỹ thuật cho từng lựa chọn.
  • Nắm workflow đầy đủ từ login (tạo session) đến authenticated request (lookup session) với Redis.
  • Viết được middleware session cơ bản bằng Python (redis.asyncio + FastAPI) với sliding window TTL.
  • Phân biệt session store với cache store và hiểu tại sao session cần durability cao hơn.
  • Nhận diện các anti-pattern session phổ biến nhất.
  • Nắm roadmap Module 7: các bài tiếp theo sẽ đào sâu vào từng khía cạnh.
2

Session Là Gì — Refresher

HTTP là stateless: mỗi request độc lập, server không tự biết request đó đến từ ai. Session là cơ chế server-side dùng để duy trì state sau khi user đăng nhập.

Luồng cơ bản:

  1. User POST credentials → server xác thực → tạo session ID (chuỗi ngẫu nhiên).
  2. Server lưu session data vào một nơi nào đó, key là session ID.
  3. Server trả Set-Cookie: session_id=abc123; HttpOnly; Secure cho browser.
  4. Mỗi request tiếp theo: browser tự gắn cookie → server nhận session ID → lookup → lấy ra data của user.

Dữ liệu thường lưu trong session:

  • user_id, role, permissions — để authorization.
  • last_activity — để sliding TTL và timeout.
  • csrf_token — bảo vệ chống CSRF attack.
  • Cart data (e-commerce nhỏ), wizard state, preferences tạm thời.

Session là stateful: server giữ state, client chỉ cầm ID. Đây là điểm khác biệt với stateless JWT — sẽ so sánh chi tiết ở bài 77.

3

4 Lựa Chọn Lưu Session

Về mặt kỹ thuật có 4 chỗ lưu session phổ biến:

Lựa chọn Latency Multi-instance Restart safety TTL native Scale
In-memory app ~0 (local) Không Không Không Không
SQL DB 1–5 ms Không (cron) Giới hạn
NoSQL document 2–10 ms Một phần Tốt
Redis < 1 ms Tùy cấu hình Có (native) Tốt (cluster)

Mỗi lựa chọn có trường hợp hợp lý riêng; phần tiếp theo phân tích từng cái.

4

In-Memory App — Vì Sao Không Đủ

Lưu session trong RAM của chính app process là cách đơn giản nhất và nhanh nhất về latency (không cần network). Nhưng có 4 vấn đề cơ bản:

Multi-instance

Khi chạy nhiều instance (horizontal scale), load balancer phân phối request theo round-robin hoặc random. User A đăng nhập vào instance 1 → session nằm trong RAM của instance 1. Request tiếp theo của user A có thể đi vào instance 2 → không tìm thấy session → "chưa đăng nhập".

Workaround thông thường là sticky session: load balancer luôn gửi request của một user về cùng một instance. Nhưng sticky session làm giảm tác dụng của horizontal scale (load không đều) và gây vấn đề nghiêm trọng khi một instance sập — toàn bộ user của instance đó bị đăng xuất.

Restart mất hết

Deploy phiên bản mới, crash, OOM kill — bất kỳ lý do nào khiến process restart đều xóa sạch toàn bộ session. User phải đăng nhập lại, đặc biệt tệ với ứng dụng có phiên làm việc dài (banking, admin panel).

Memory limit

1 triệu user active × 2 KB/session = 2 GB memory, ăn vào heap của app. Nếu session chứa thêm cart, permissions list thì có thể lên 5–10 KB/session. Memory của app process phải dùng cho logic, không nên dùng làm session store.

Không có TTL native

Phải tự implement cleanup timer, thread, hoặc background goroutine để scan và xóa session hết hạn. Dễ bị memory leak nếu cleanup logic có bug.

5

DB-Based Session — Vì Sao Chậm

Lưu session vào SQL database (bảng sessions) giải quyết được vấn đề multi-instance và restart. Nhưng sinh ra vấn đề mới: mỗi authenticated request phải đọc DB.

-- Mỗi request gửi lên đều phải chạy
SELECT user_id, role, permissions, last_activity
FROM sessions
WHERE id = 'abc123'
  AND expires_at > NOW();

-- Và update last_activity
UPDATE sessions
SET last_activity = NOW()
WHERE id = 'abc123';

Với PostgreSQL được tune tốt, một index lookup mất khoảng 1–5 ms. Nghe có vẻ ổn, nhưng:

  • 10.000 req/s = 10.000 SELECT + 10.000 UPDATE chỉ cho session lookup, chưa kể các query business logic thực sự.
  • UPDATE last_activity mỗi request sinh ra WRITE I/O cao, gây WAL churn liên tục.
  • DB bị kéo vào critical path của mọi request, trở thành single point of failure thứ hai.
  • Cleanup session hết hạn cần scheduled job: DELETE FROM sessions WHERE expires_at < NOW(). Nếu bảng lớn, DELETE này có thể lock.

NoSQL document store (MongoDB, DynamoDB) tốt hơn SQL cho use case này vì schema linh hoạt và throughput cao hơn ở write. Nhưng latency vẫn cao hơn Redis đáng kể khi so sánh sub-millisecond vs multi-millisecond.

6

Redis-Based Session — Vì Sao Phù Hợp

Redis giải quyết đồng thời tất cả các vấn đề trên:

Latency < 1 ms

GET session:abc123 trả về trong vài trăm microsecond. Dữ liệu nằm trong RAM, không có disk I/O cho hot data.

TTL native

SET session:abc123 "{...json...}" EX 3600

Redis tự xóa key khi hết TTL — không cần cron, không cần cleanup job, không cần lock.

Multi-instance ngay từ đầu

Tất cả instance app connect vào cùng một Redis. Session lookup hoạt động bất kể request đến instance nào. Không cần sticky session.

Persistence tùy chọn

AOF (Append-Only File) với appendfsync everysec: Redis ghi WAL mỗi giây, crash recovery khôi phục được session. Phù hợp khi session là source of truth.

Atomic operations

Update một field trong session không cần đọc toàn bộ rồi ghi lại:

HSET session:abc123 last_activity 1748764800
EXPIRE session:abc123 3600

Cluster scale ngang

Redis Cluster shard dữ liệu theo hash slot. Thêm node → tăng capacity tuyến tính.

7

Session vs Cache — Khác Biệt Quan Trọng

Nhiều team dùng cùng một Redis instance cho cả cache và session. Điều đó hợp lý, nhưng cần hiểu rõ sự khác biệt giữa hai loại data:

Đặc điểm Cache Session store
Source of truth Không (DB là nguồn gốc) Có (với stateful session)
Khi miss Recompute từ DB, không sao User phải re-authenticate
Eviction policy allkeys-lru chấp nhận được noeviction hoặc volatile-lru
Persistence Không bắt buộc AOF khuyến nghị
Mất data khi restart Cold cache, hậu quả nhẹ Toàn user bị logout, hậu quả nặng

Nếu dùng chung một Redis instance, hãy cấu hình maxmemory-policy volatile-lru (chỉ evict key có TTL khi thiếu memory) và bật AOF. Tốt hơn là dùng hai instance riêng với cấu hình khác nhau.

Trade-off persistence: Redis với AOF everysec chậm hơn một chút so với cache-only Redis (không AOF), nhưng đổi lại mất tối đa 1 giây data khi crash — thường chấp nhận được cho session.

8

Workflow Session Với Redis

Login — tạo session

POST /login {email, password}
    ↓
Verify credentials (DB lookup)
    ↓
Generate session_id = secrets.token_urlsafe(32)   # 256-bit entropy
    ↓
SET session:{session_id} "{user_id:1, role:admin, ...}" EX 3600
    ↓
Response: Set-Cookie: session_id={session_id}; HttpOnly; Secure; SameSite=Strict

Authenticated request — lookup session

GET /profile
Cookie: session_id=abc123
    ↓
GET session:abc123
    ↓ (HIT)
Deserialize → user data
    ↓
EXPIRE session:abc123 3600    # sliding window: reset TTL
    ↓
Process request với user context

Logout — xóa session

POST /logout
Cookie: session_id=abc123
    ↓
DEL session:abc123
    ↓
Response: Clear-Cookie + 200 OK

Logout bằng DEL là cách duy nhất đảm bảo session bị vô hiệu hóa ngay lập tức — không cần chờ TTL tự hết.

9

Code Python — Middleware FastAPI

import secrets
import json
from fastapi import FastAPI, Request, HTTPException, Response
from pydantic import BaseModel
import redis.asyncio as aioredis

app = FastAPI()
r = aioredis.from_url("redis://localhost:6379", decode_responses=True)
SESSION_TTL = 3600  # 1 giờ


# --- Dependency: lấy session từ cookie ---
async def get_session(request: Request) -> dict | None:
    sid = request.cookies.get("session_id")
    if not sid:
        return None
    raw = await r.get(f"session:{sid}")
    if not raw:
        return None
    # Sliding window: reset TTL mỗi lần user active
    await r.expire(f"session:{sid}", SESSION_TTL)
    return json.loads(raw)


async def require_session(request: Request) -> dict:
    session = await get_session(request)
    if not session:
        raise HTTPException(status_code=401, detail="Not authenticated")
    return session


# --- Login ---
class Credentials(BaseModel):
    email: str
    password: str


@app.post("/login")
async def login(credentials: Credentials, response: Response):
    # verify_credentials trả về User object hoặc raise HTTPException
    user = await verify_credentials(credentials.email, credentials.password)

    sid = secrets.token_urlsafe(32)  # 256-bit, URL-safe, crypto-random
    session_data = {
        "user_id": user.id,
        "role": user.role,
        "email": user.email,
    }
    await r.set(
        f"session:{sid}",
        json.dumps(session_data),
        ex=SESSION_TTL,
    )
    response.set_cookie(
        key="session_id",
        value=sid,
        httponly=True,
        secure=True,
        samesite="strict",
        max_age=SESSION_TTL,
    )
    return {"ok": True}


# --- Protected endpoint ---
@app.get("/profile")
async def get_profile(request: Request):
    session = await require_session(request)
    # session = {"user_id": 1, "role": "admin", "email": "..."}
    return {"user_id": session["user_id"], "role": session["role"]}


# --- Logout ---
@app.post("/logout")
async def logout(request: Request, response: Response):
    sid = request.cookies.get("session_id")
    if sid:
        await r.delete(f"session:{sid}")
    response.delete_cookie("session_id")
    return {"ok": True}

Một số điểm quan trọng:

  • decode_responses=True: redis-py trả về str thay vì bytes, không cần decode thủ công.
  • r.expire sau mỗi GET thực hiện sliding window: user active thì session không bao giờ hết hạn sớm.
  • Nếu muốn absolute TTL (session hết hạn đúng X giờ sau login bất kể hoạt động), bỏ dòng r.expire đi.
  • Lệnh r.set(..., ex=SESSION_TTL) là atomic: SET + EXPIRE trong một command — không có race condition giữa SET và EXPIRE.
10

Session ID Generation — Security

Session ID phải đủ ngẫu nhiên để không thể đoán hoặc brute force. Yêu cầu tối thiểu: 128-bit entropy.

Python

import secrets
sid = secrets.token_urlsafe(32)
# 32 bytes = 256 bits. token_urlsafe dùng os.urandom() -> CSPRNG.
# Output: chuỗi base64url ~43 ký tự

Node.js

const { randomBytes } = require("crypto");
const sid = randomBytes(32).toString("hex");
// 32 bytes = 256 bits. randomBytes dùng OS CSPRNG.

Những gì KHÔNG nên dùng

  • UUID v4: uuid.uuid4() chỉ có 122-bit entropy và một số byte là fixed. Không sai về mặt kỹ thuật nhưng không phải lựa chọn tốt nhất cho security-sensitive ID.
  • Sequential / timestamp-based: dễ đoán, dễ enumerate. Ví dụ: session ID = user_id + timestamp là sai hoàn toàn.
  • Math.random() / random.random(): không phải CSPRNG (Cryptographically Secure Pseudo-Random Number Generator).

Session fixation

Sau khi login thành công, luôn rotate session ID: xóa session cũ (nếu có), tạo session ID mới. Điều này ngăn attacker "fix" session ID trước khi user đăng nhập rồi sử dụng session đó.

@app.post("/login")
async def login(request: Request, credentials: Credentials, response: Response):
    # Xóa session cũ nếu tồn tại
    old_sid = request.cookies.get("session_id")
    if old_sid:
        await r.delete(f"session:{old_sid}")

    user = await verify_credentials(credentials.email, credentials.password)
    sid = secrets.token_urlsafe(32)  # Session ID mới sau login
    # ... tiếp tục như cũ
11

Session Storage Format: JSON vs Hash

Có hai cách lưu session data trong Redis:

JSON String (GET/SET)

SET session:abc123 '{"user_id":1,"role":"admin","last_seen":1748764800}' EX 3600
GET session:abc123  # trả về toàn bộ JSON

Ưu điểm: đơn giản, một lệnh GET là có toàn bộ data. Nhược điểm: mỗi lần update một field nhỏ (ví dụ last_seen) phải GET toàn bộ, deserialize, update, serialize rồi SET lại — race condition tiềm ẩn nếu có concurrent update.

Hash (HSET/HGETALL)

HSET session:abc123 user_id 1 role admin last_seen 1748764800
EXPIRE session:abc123 3600
HGETALL session:abc123  # trả về tất cả field

# Update một field mà không đọc cả session
HSET session:abc123 last_seen 1748768400
EXPIRE session:abc123 3600

Ưu điểm: update field nhỏ không rewrite cả session, atomic per-field. Redis 7.4 trở lên hỗ trợ HEXPIRE để đặt TTL per-field (nếu cần). Nhược điểm: cần thêm một lệnh EXPIRE riêng (không atomic với HSET, trừ khi dùng pipeline hoặc script Lua).

Khi nào dùng gì

  • JSON: session nhỏ (< 1 KB), ít update riêng lẻ, code đơn giản hơn.
  • Hash: session có nhiều field thay đổi độc lập, hoặc cần HEXPIRE (Redis 7.4+).
12

TTL Strategies

Ba chiến lược TTL phổ biến cho session:

Absolute TTL

TTL đặt khi tạo session, không bao giờ reset. Session hết hạn đúng X giờ sau khi đăng nhập bất kể user có active hay không.

await r.set(f"session:{sid}", data, ex=86400)  # 24 giờ, không refresh

Dùng khi cần "force re-login mỗi 24 giờ" vì lý do compliance hoặc security.

Sliding Window

Mỗi lần user gửi request, TTL được reset về X. User active liên tục thì session không bao giờ hết hạn sớm. Session chỉ expire nếu user không hoạt động trong X giây liên tục.

raw = await r.get(f"session:{sid}")
if raw:
    await r.expire(f"session:{sid}", SESSION_TTL)  # reset TTL

Hybrid (Sliding + Absolute Cap)

Kết hợp: TTL reset mỗi request (sliding), nhưng có một created_at trong session data. Nếu now - created_at > max_lifetime thì từ chối dù TTL Redis chưa hết.

session = json.loads(raw)
if time.time() - session["created_at"] > MAX_SESSION_LIFETIME:
    await r.delete(f"session:{sid}")
    raise HTTPException(status_code=401, detail="Session expired, please re-login")

Chiến lược này sẽ được phân tích chi tiết ở bài 79.

13

Persistence — AOF Cho Session

Session là source of truth — nếu Redis mất dữ liệu sau crash, toàn bộ user đang đăng nhập sẽ bị logout. Với ứng dụng có nhiều user active, đây là trải nghiệm tệ.

Cấu hình khuyến nghị cho Redis session store:

# redis.conf
appendonly yes
appendfsync everysec      # fsync mỗi giây; max mất 1 giây data khi crash
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# Không bắt buộc dùng RDB snapshot cho session,
# nhưng nếu bật thì không hại gì:
save ""                   # tắt RDB nếu chỉ dùng AOF

So sánh giữa các mode persistence với session:

  • No persistence: restart là logout tất cả. Không phù hợp cho production session store.
  • RDB snapshot: tạo snapshot mỗi 5–60 phút; crash có thể mất vài phút session. Chấp nhận được nếu ứng dụng tolerate re-login.
  • AOF everysec: mất tối đa 1 giây data. Phù hợp cho hầu hết production session.
  • AOF always: mất 0 data, nhưng mỗi write phải fsync — throughput giảm đáng kể; hiếm khi cần thiết cho session.
14

Scale Session Store

Ước tính capacity:

  • 1 triệu user active × 2 KB/session = 2 GB memory.
  • 10 triệu user active × 2 KB/session = 20 GB memory.
  • Redis single instance (64–256 GB RAM): đủ cho hàng chục triệu session.

Redis single instance với replication đủ cho hầu hết hệ thống. Khi cần hơn, Redis Cluster shard theo hash slot của session key, scale ngang tuyến tính.

Key design với Cluster: session:{session_id} — session ID là random nên hash tự nhiên phân đều qua các slot. Không cần hash tag.

Replication set up tối thiểu cho production: 1 primary + 1 replica, tránh single point of failure. Sentinel hoặc Cluster quản lý failover tự động.

15

Security Considerations

Cookie attributes

  • HttpOnly: JavaScript không đọc được cookie — ngăn XSS đánh cắp session ID.
  • Secure: cookie chỉ gửi qua HTTPS.
  • SameSite=Strict: không gửi cookie khi request từ trang khác — ngăn CSRF (cần xem xét nếu app có OAuth redirect).
  • Domain scoping: không đặt domain quá rộng (ví dụ .example.com cho toàn bộ subdomain khi không cần thiết).

CSRF protection

SameSite=Strict giảm nguy cơ CSRF, nhưng không đủ cho mọi trường hợp (ví dụ cross-origin request hợp lệ). Lưu CSRF token trong session và validate trên mỗi mutation request (POST, PUT, DELETE).

import secrets

# Khi tạo session:
session_data["csrf_token"] = secrets.token_urlsafe(32)

# Khi handle mutation request:
request_csrf = request.headers.get("X-CSRF-Token")
if not secrets.compare_digest(session["csrf_token"], request_csrf or ""):
    raise HTTPException(status_code=403, detail="CSRF validation failed")

Session hijacking

Một số hệ thống bind session với IP hoặc User-Agent. Trade-off: làm phiền user mobile (IP thay đổi khi chuyển mạng). Thay vào đó, nên ưu tiên HTTPS + HttpOnly cookie + session rotation định kỳ.

16

Cleanup & Memcached So Sánh

Cleanup expired sessions

Redis dùng hai cơ chế xóa key hết TTL:

  • Passive expiration: key bị xóa khi được truy cập lần tiếp theo và Redis phát hiện đã hết hạn.
  • Active expiration: Redis chạy background task mỗi 100ms, sample ngẫu nhiên 20 key có TTL, xóa những key đã hết hạn. Nếu > 25% key trong batch đã hết hạn, tiếp tục sample thêm.

Không cần viết cron job hay scheduled task để cleanup session. Không cần SCAN tìm expired key — tự Redis lo.

Redis vs Memcached cho session

  • Memcached: chỉ hỗ trợ string + TTL. Không có persistence, không có data structure, không có atomic hash update. Phù hợp khi session đơn giản và không cần durability.
  • Redis: đầy đủ data type (Hash, String, List), persistence (AOF), pub/sub (có thể notify session event), atomic ops. Đây là lý do Redis dominant trong production session store.
17

Khi Nào Không Cần Redis Session

  • Pure stateless JWT: nếu app dùng JWT và không cần revocation, không cần session store. Server verify signature là đủ. Trade-off và giới hạn của JWT revocation sẽ phân tích ở bài 77.
  • Small monolith single-instance: nếu chỉ chạy một process, không có plan scale ngang, không cần high availability, in-memory session đủ dùng. Nhưng cần cân nhắc restart policy.
  • Public read-heavy service không có user auth: không có login → không có session.

Redis session không phải mandatory cho mọi ứng dụng. Nó phù hợp nhất khi: multi-instance, cần revocation ngay lập tức, hoặc session chứa data phức tạp cần update riêng lẻ.

18

Anti-patterns Cần Tránh

  • Lưu sensitive data plaintext trong session: không lưu password hash, credit card, PII không cần thiết vào session. Nếu Redis bị truy cập trái phép, session data lộ hết. Chỉ lưu những gì cần để authorization.
  • Session ID ngắn hoặc predictable: session ID < 128-bit entropy có thể bị brute force. Sequential ID (user_id + counter) cho phép enumerate session của user khác.
  • Không đặt TTL: session không TTL → Redis memory tăng vô hạn theo thời gian. Luôn SET với EX hoặc gọi EXPIRE sau khi tạo session.
  • Không persist Redis session store: Redis restart → toàn bộ user bị logout. Đặc biệt tệ khi deploy, update Redis version, hoặc crash. Bật AOF cho Redis lưu session.
  • Session blob quá lớn: lưu hàng chục KB vào session (ví dụ toàn bộ profile JSON, permission tree lớn) lãng phí Redis memory và tăng latency serialize/deserialize. Chỉ lưu những field cần thiết cho mỗi request.
  • Dùng allkeys-lru cho Redis session: nếu Redis hết memory và evict session key, user bị logout mà không có thông báo. Dùng volatile-lru (chỉ evict key có TTL) hoặc noeviction + monitor memory.
19

Tổng Kết & Quiz

Tổng kết

  • Session là server-side state sau login; client chỉ giữ session ID trong cookie.
  • In-memory app không đủ cho multi-instance và không survive restart. SQL DB tạo write/read load trên mỗi request. Redis giải quyết cả hai: < 1 ms latency, TTL native, distributed, persistence tùy chọn.
  • Session khác cache: nếu miss, user phải re-authenticate — cần durability cao hơn. Dùng AOF everysec, eviction policy volatile-lru hoặc noeviction.
  • Session ID phải dùng CSPRNG với ít nhất 128-bit entropy. Rotate sau login để chống session fixation.
  • TTL: absolute cho compliance, sliding window cho UX, hybrid cho cả hai.
  • Redis tự cleanup expired key — không cần cron job.
  • Tránh: lưu sensitive data plaintext, không TTL, session blob quá lớn, không persist Redis.

Quiz 5 câu

  1. Vì sao sticky session workaround cho in-memory session gây vấn đề khi một instance sập?
  2. Tính toán: nếu có 500.000 user active, mỗi session 3 KB, cần bao nhiêu memory Redis? Single instance có đủ không?
  3. Sự khác biệt giữa eviction policy allkeys-lruvolatile-lru ảnh hưởng thế nào đến session store?
  4. Tại sao r.set(key, value, ex=TTL) an toàn hơn r.set(key, value) rồi r.expire(key, TTL) khi tạo session?
  5. Mô tả session fixation attack và cách rotate session ID sau login ngăn chặn nó.

Đáp án gợi ý

  1. Khi instance sập, load balancer ngừng gửi request đến instance đó. Nhưng toàn bộ session đang lưu trong RAM của instance đó bị mất theo — user đang active phải đăng nhập lại. Sticky session cũng làm load không đều giữa các instance: instance nào được sticky nhiều sẽ bị quá tải.
  2. 500.000 × 3 KB = 1,5 GB. Single Redis instance (ngay cả cấu hình nhỏ 4–8 GB RAM) đủ dùng. Redis single instance thực tế handle được hàng chục triệu session.
  3. allkeys-lru: Redis có thể evict bất kỳ key nào (kể cả session key) khi hết memory — user bị logout không báo trước. volatile-lru: chỉ evict key có TTL; session key có TTL có thể bị evict nếu gần hết TTL nhất, nhưng ít nguy hiểm hơn. noeviction: từ chối write mới khi đầy — app nhận lỗi thay vì bị evict silent.
  4. SET key value EX ttl là một lệnh atomic. Nếu process crash sau SET nhưng trước EXPIRE, key sẽ tồn tại vĩnh viễn (không bao giờ expire). Dùng EX trong một lệnh đảm bảo TTL luôn được đặt cùng lúc với value.
  5. Attacker gửi session ID đã biết đến victim (ví dụ qua URL parameter). Victim đăng nhập → server dùng session ID đó để lưu session. Attacker dùng session ID đã biết để truy cập → có quyền truy cập của victim. Rotate session ID sau login: DEL session cũ, tạo session ID mới → attacker không còn biết ID hợp lệ.

Bài tiếp theo

Bài 77 so sánh chi tiết stateful session (server lưu state, client giữ ID) với stateless JWT (client giữ signed token, server không lưu) — khi nào dùng cái nào, trade-off revocation và overhead.

Tham khảo