Cookie 和 Session 是 Web 會話管理的雙基石。本文從 HTTP 無狀態本質切入,系統對比兩者機制、存儲位置、生命周期、安全性、跨域行為,並給出分布式 Session 共享方案。
HTTP 協議本質是無狀態的:每個請求獨立,服務端不記住客戶端身份。為了識別用戶狀態(登錄態、購物車、偏好設置),引入了會話管理機制:
| 維度 | Cookie | Session |
|---|---|---|
| 存儲位置 | 瀏覽器端 (文件/內存) | 服務端內存/Redis/數據庫 |
| 存儲容量 | 單個 ≤ 4KB,域名下 ≤ 20-50 個 | 理論無上限 (受限服務端內存) |
| 生命周期 | 可設置過期時間 (持久/會話級) | 默認 30 分鐘無操作過期,可配置 |
| 安全性 | 低:可被竊取、篡改、XSS 獲取 | 高:數據不離開服務端,僅傳 ID |
| 帶寬影響 | 每請求攜帶,增加流量 | 僅傳 SessionID (極小) |
| 跨域支持 | 受同源策略限制,可配置 Domain/Path | 需共享存儲或 Token 方案 |
| 禁用應對 | 用戶可禁用 Cookie → 失效 | 可用 URL 重寫 (jsessionid=xxx) 兼容 |
| 典型用途 | 記住我、偏好設置、追蹤、CSRF Token | 用戶身份、權限、購物車、敏感業務數據 |
// 服務端設置 Cookie (Servlet)
Cookie cookie = new Cookie("userId", "12345");
cookie.setMaxAge(60 * 60 * 24 * 7); // 7天過期,秒為單位;-1=會話級;0=刪除
cookie.setPath("/"); // 生效路徑
cookie.setDomain(".example.com"); // 跨子域共享
cookie.setHttpOnly(true); // 防 XSS:JS 無法讀取
cookie.setSecure(true); // 僅 HTTPS 傳輸
response.addCookie(cookie);
// 瀏覽器自動攜帶
// Request Headers: Cookie: userId=12345; JSESSIONID=ABC...
// 服務端創建/獲取 Session
HttpSession session = request.getSession(); // 無則創建,有則返回
HttpSession session = request.getSession(false); // 無則返回 null
// 存取數據
session.setAttribute("user", userObj);
User user = (User) session.getAttribute("user");
// 設置超時 (秒)
session.setMaxInactiveInterval(30 * 60); // 30分鐘
// 銷毀
session.invalidate();
// 底層流程:
// 1. 首次請求 → 服務端生成 SessionID (UUID) → 存入內存 Map
// 2. 通過 Set-Cookie: JSESSIONID=xxx 發送給瀏覽器
// 3. 後續請求 → 瀏覽器自動攜帶 Cookie: JSESSIONID=xxx
// 4. 服務端解析 Cookie → 從 Map 獲取 SessionData
JSESSIONID=abc 在 Cookie 中/app/action;jsessionid=abc (Cookie 被禁用時備選)<input type="hidden" name="jsessionid" value="abc"> (極少用)Servlet 容器自動處理,開發者無感知。response.encodeURL(url) 可自動追加 SessionID。
Cookie cookie = new Cookie("token", "xxx");
cookie.setHttpOnly(true); // 必須:防 XSS 竊取
cookie.setSecure(true); // 必須:僅 HTTPS (生產環境)
cookie.setPath("/"); // 最小權限路徑
cookie.setDomain(".api.example.com"); // 最小權限域名
cookie.setMaxAge(3600); // 短期過期
// SameSite 屬性 (Servlet 2.5+ 需手動設置 Header)
// response.addHeader("Set-Cookie", "token=xxx; SameSite=Strict; Secure; HttpOnly");
request.changeSessionId() 防 Session Fixation單體應用 Session 存內存,集群下請求分發到不同節點導致 Session 丟失。
| 方案 | 原理 | 優點 | 缺點 |
|---|---|---|---|
| Sticky Session (IP Hash) | Nginx 將同一用戶固定路由到同一節點 | 零代碼改動,性能最好 | 節點故障 Session 丟失,擴容不均 |
| Session Replication (Tomcat Cluster) | 節點間廣播同步 Session | 容器級透明 | 網絡風暴,節點>5 不推薦 |
| Redis 共享存儲 (主流) | Session 序列化存 Redis,所有節點讀寫 | 高可用、橫向擴展、數據持久化 | 網絡開銷、序列化成本、單點風險(需哨兵/集群) |
| Database 共享存儲 | Session 存 MySQL/PostgreSQL | ACID、持久化 | 性能差、不適合高併發 |
| JWT / Token 無狀態 | 用戶信息簽名存客戶端,服務端無狀態 | 完全無狀態、極易擴展、跨域友好 | 無法主動失效(需黑名單)、Token體積大、續簽邏輯複雜 |
org.springframework.session
spring-session-data-redis
spring:
session:
store-type: redis
redis:
namespace: spring:session
flush-mode: on_save
redis:
host: localhost
port: 6379