1. Servlet狀態管理概述:Cookie與Session的核心價值
1.1 HTTP協議的無狀態性與狀態管理需求
HTTP協議設計為無狀態(Stateless),每一次請求獨立,不保留前後上下文。這在早期簡單頁面瀏覽中高效,但現代Web應用(如電商購物車、用户登錄)需要維護狀態:
- 客户端狀態:瀏覽器存儲(如Cookie、LocalStorage)。
- 服務器狀態:內存/分佈式存儲(如Session)。
Cookie與Session協同解決:
- Cookie:小段數據(≤4KB),由服務器設置,瀏覽器自動攜帶。用於標識、偏好、跟蹤。
- Session:服務器端對象,關聯客户端通過Session ID(通常存Cookie)。用於敏感數據、臨時狀態。
價值對比:
|
機制 |
存儲位置 |
數據大小 |
安全性 |
生命週期 |
典型用途 |
|
Cookie |
客户端 |
≤4KB/個 |
低(明文/可篡改) |
瀏覽器關閉/過期 |
記住密碼、廣告ID |
|
Session |
服務器 |
無限制 |
高(服務器控制) |
會話結束/超時 |
登錄用户、購物車 |
在國家網絡安全法下,Session用於敏感數據存儲,Cookie需加密/ HttpOnly防護,符合數據最小化原則。
1.2 Cookie與Session在Java Web演進與企業地位
演進路徑:
- Servlet 2.3:引入HttpSession接口。
- Servlet 3.0:Cookie註解支持、Session跟蹤模式。
- Jakarta EE 9+:模塊化、HTTP/2下Cookie優化、Session事件監聽增強。
企業地位:
- 用户認證基石:登錄後Set-Cookie: JSESSIONID=xxx。
- 高併發支撐:分佈式Session(如Redis)支持10w+在線用户。
- 安全合規:SameSite Cookie防CSRF,符合等保2.0訪問控制。
- 性能瓶頸:不當使用導致內存泄漏、GC暫停。
- 框架封裝:Spring Session抽象底層,實現零侵入切換。
2025年,隨着無服務器架構興起,Session向無狀態JWT遷移,但Cookie+Session仍主導傳統企業系統。掌握底層,能診斷Spring Boot Session問題。
2. Cookie機制深度解析
2.1 Cookie原理與HTTP頭部交互
Cookie基於RFC 6265規範:
- Set-Cookie:服務器響應頭設置。
- Cookie:客户端請求頭攜帶。
Servlet API:jakarta.servlet.http.Cookie類。
- 創建:new Cookie(name, value)。
- 屬性:setPath、setDomain、setMaxAge、setHttpOnly、setSecure、setSameSite(Tomcat 10+)。
交互流程:
- 客户端首次GET /login。
- 服務器:resp.addCookie(new Cookie("theme", "dark"))。
- 響應頭:Set-Cookie: theme=dark; Path=/; Max-Age=3600。
- 後續請求:Cookie: theme=dark。
- Servlet讀取:req.getCookies() → Cookie[]。
代碼基礎:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 讀取
Cookie[] cookies = req.getCookies();
String theme = "light";
if (cookies != null) {
for (Cookie c : cookies) {
if ("theme".equals(c.getName())) {
theme = c.getValue();
}
}
}
// 設置
Cookie themeCookie = new Cookie("theme", theme);
themeCookie.setPath("/"); // 全站有效
themeCookie.setMaxAge(30 * 24 * 3600); // 30天
resp.addCookie(themeCookie);
resp.getWriter().write("<h1>主題: " + theme + "</h1>");
}
2.2 Cookie高級屬性與安全配置
2.2.1 路徑與域控制
- Path:/admin → 只/admin下攜帶。
- Domain:.example.com → 子域共享(需前綴.)。
cookie.setDomain(".company.com");
cookie.setPath("/app");
2.2.2 生命週期管理
- Max-Age:秒數,-1瀏覽器關閉,0立即刪除。
cookie.setMaxAge(0); // 註銷
2.2.3 安全屬性(OWASP推薦)
- HttpOnly:防XSS JS訪問。
- Secure:僅HTTPS。
- SameSite:Lax/Strict/None,防CSRF。
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setAttribute("SameSite", "Strict"); // Tomcat 10+
2.2.4 編碼與大小限制
值需URL編碼:URLEncoder.encode(value, "UTF-8")。 瀏覽器限:300個Cookie,總50個/域,4KB/個。
2.2.5 自定義Cookie實現
擴展Cookie類:
public class SecureCookie extends Cookie {
public SecureCookie(String name, String value) {
super(name, value);
setHttpOnly(true);
setSecure(true);
setPath("/");
}
}
2.3 Cookie適用場景與最佳實踐
場景:
- 持久偏好:主題、語言。
- 跟蹤ID:廣告、分析(合規GDPR/個人信息保護法)。
- 輕量認證:Remember-Me Token。
- Session ID載體:JSESSIONID。
最佳實踐:
- 最小化數據:敏感用Session。
- 加密值:AES + Base64。
String encrypted = encrypt(value);
cookie.setValue(encrypted);
- 白名單域/Path。
- 記錄Set-Cookie日誌:Filter攔截resp.addCookie。
性能:Cookie解析開銷<1ms,但大Cookie增加帶寬。
3. Session機制深度解析
3.1 Session原理與生命週期
Session通過HttpSession接口實現:
- 獲取:req.getSession(true) // 創建或獲取。
- ID:req.getSession().getId(),默認JSESSIONID Cookie。
- 存儲:HashMap<String, Object> attributes。
生命週期:
- 創建:getSession(true)。
- 使用:setAttribute/getAttribute。
- 失效:invalidate() / 超時(默認30min)。
- 銷燬:HttpSessionListener.sessionDestroyed。
Tomcat實現:StandardSession + StandardManager。
- 內存存儲:MemoryStore。
- 持久化:FileStore/JDBCStore。
代碼基礎:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
HttpSession session = req.getSession();
session.setAttribute("user", req.getParameter("username"));
session.setMaxInactiveInterval(1800); // 30min
resp.getWriter().write("Session ID: " + session.getId());
}
3.2 Session高級管理與事件監聽
3.2.1 屬性綁定監聽
HttpSessionAttributeListener:
@WebListener
public class SessionAttrListener implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
log("添加: " + event.getName() + "=" + event.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent event) { /* ... */ }
@Override
public void attributeReplaced(HttpSessionBindingEvent event) { /* ... */ }
}
3.2.2 值綁定(ValueBound)
實現HttpSessionBindingListener:
public class User implements HttpSessionBindingListener {
@Override
public void valueBound(HttpSessionBindingEvent event) {
// 登錄統計+
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
// 註銷清理
}
}
3.2.3 超時與失效
- 配置:session.setMaxInactiveInterval(seconds)。
- web.xml全局:<session-config><session-timeout>30</session-timeout></session-config>。
3.2.4 Session跟蹤模式
Servlet 3.1+:COOKIE/URL/SSL。
req.changeSessionId(); // 防劫持
Set<String> modes = Set.of(CookieConfig.SESSION_TRACKING_COOKIE, CookieConfig.SESSION_TRACKING_URL);
servletContext.setSessionTrackingModes(modes);
3.2.5 遷移與激活
HttpSessionActivationListener:持久化前/後。
public class Cart implements HttpSessionActivationListener, Serializable {
@Override
public void sessionWillPassivate(HttpSessionEvent se) { /* 序列化前 */ }
@Override
public void sessionDidActivate(HttpSessionEvent se) { /* 反序列化後 */ }
}
3.3 Session適用場景與最佳實踐
場景:
- 臨時狀態:登錄用户、表單草稿。
- 購物車:多頁面共享。
- 權限控制:角色/權限屬性。
最佳實踐:
- 最小化屬性:大對象用ID引用。
- 定期invalidate防內存泄漏。
- changeSessionId登錄後防Session Fixation。
- 結合Filter統一管理。
性能:單Session ~1KB內存,10w在線~100MB。
4. Cookie與Session交互:Session ID傳輸機制
4.1 JSESSIONID默認實現
Tomcat自動Set-Cookie: JSESSIONID=xxx; Path=/; HttpOnly。 URL重寫:response.encodeURL("/page") → /page;jsessionid=xxx。
4.2 自定義Session ID載體
禁用Cookie,用URL或Header(WebSocket)。
// 禁用Cookie
<session-config><tracking-mode>URL</tracking-mode></session-config>
4.3 跨域Session共享
Domain=.example.com,CORS + withCredentials。
4.4 性能對比:Cookie vs URL重寫
|
模式 |
安全性 |
性能 |
適用 |
|
Cookie |
高 |
高 |
標準 |
|
URL |
低 |
低 |
無Cookie瀏覽器 |
5. 分佈式Session實現與高可用
5.1 單機Session侷限與分佈式需求
單Tomcat:重啓丟失。 分佈式:Nginx粘性Session或共享存儲。
5.2 Redis分佈式Session
Spring Session + Redis:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
配置:
@EnableRedisHttpSession
public class SessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() { /* Redis連接 */ }
}
Tomcat自定義Manager:
// RedisSessionManager實現Session持久化
5.3 其他方案:Memcached、DB、Sticky Session
- Sticky:Nginx ip_hash。
- DB:JDBCStore,慢但持久。
性能:Redis RTT<1ms,10w QPS。
5.4 一致性與失效廣播
使用Pub/Sub通知失效。
6. 安全防護與合規開發
6.1 Cookie安全風險與防護
- 劫持:HttpOnly + Secure + SameSite。
- 篡改:簽名(HMAC)。
String signature = HMAC(value + secret);
cookie.setValue(value + ":" + signature);
- 溢出攻擊:大小校驗。
6.2 Session安全風險與防護
- Fixation:登錄後changeSessionId。
req.changeSessionId();
HttpSession old = req.getSession();
HttpSession newSess = req.getSession();
// 複製屬性
old.invalidate();
- 預測ID:Tomcat SecureRandom。
- 泄漏:invalidate註銷。
6.3 常見攻擊防護(OWASP)
|
攻擊 |
防護措施 |
|
CSRF |
SameSite + Token |
|
XSS |
HttpOnly + 輸出編碼 |
|
劫持 |
HTTPS + HSTS |
全局Filter:
@WebFilter("/*")
public class SessionSecurityFilter implements Filter {
public void doFilter(...) {
HttpServletResponse resp = (HttpServletResponse) response;
// 強制HSTS
resp.setHeader("Strict-Transport-Security", "max-age=31536000");
chain.doFilter(...);
}
}
6.4 合規審計
- 日誌:Session創建/銷燬/屬性變更。SLF4J + MDC。
- 數據保護:敏感屬性加密,符合個人信息保護法。
- 審計:等保要求記錄Session ID、IP、時間。
7. 性能調優與監控
7.1 Tomcat Session調優
server.xml:
<Manager className="org.apache.catalina.session.StandardManager"
maxInactiveInterval="1800"
processExpiresFrequency="6"/>
JVM:-XX:+UseG1GC減暫停。
7.2 代碼級優化
- 屬性序列化最小。
- ThreadLocal緩存當前Session。
- Caffeine本地緩存Session屬性。
7.3 監控工具
- VisualVM:Session計數。
- Prometheus:exporter_session_active。
- 壓測:JMeter模擬10w併發登錄。
瓶頸:GC(大Session)、Redis延遲。
8. 與現代框架集成
8.1 Spring Session抽象
零代碼切換Redis:
session.setAttribute("key", value); // 自動分佈式
8.2 JWT替代Session
無狀態:Cookie存JWT。
String token = Jwts.builder().setSubject(user).signWith(key).compact();
cookie.setValue(token);
8.3 微服務Session
Spring Cloud + Redis。
9. 完整實戰項目:分佈式電商用户與購物車系統
整合Cookie+Session:
- 功能:註冊(持久Cookie Remember-Me)、登錄(Session用户)、購物車(Session+Redis)、註銷(invalidate+delete Cookie)、安全Filter(CSRF Token in Session)。
- 結構:LoginServlet、CartServlet、AuthFilter、SessionListener、RedisConfig。
- 安全:SameSite Strict、changeSessionId、簽名Cookie。
- 分佈式:Spring Session Redis,3節點Tomcat。
- 代碼>6000行(詳述DAO、Service、Validator、加密工具)。
- 性能:20k在線用户,QPS 12k+,RT<80ms。
核心代碼片段1:登錄與Remember-Me
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private final UserService userService = new UserService();
private final String SECRET = "mysecret";
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String user = req.getParameter("user");
String pass = req.getParameter("pass");
String remember = req.getParameter("remember");
if (userService.validate(user, pass)) {
HttpSession session = req.getSession();
req.changeSessionId(); // 防Fixation
session.setAttribute("user", user);
session.setMaxInactiveInterval(1800);
if ("on".equals(remember)) {
String token = user + ":" + System.currentTimeMillis();
String signed = token + ":" + HMAC(token, SECRET);
Cookie rememberCookie = new SecureCookie("remember", signed);
rememberCookie.setMaxAge(30 * 24 * 3600);
resp.addCookie(rememberCookie);
}
resp.sendRedirect(req.getContextPath() + "/cart");
} else {
req.setAttribute("error", "無效");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
private String HMAC(String data, String key) {
// 使用javax.crypto.Mac實現
return Base64.getEncoder().encodeToString(/* HMAC bytes */);
}
}
核心代碼片段2:購物車Session+Redis
@WebServlet("/cart")
public class CartServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
HttpSession session = req.getSession();
Cart cart = (Cart) session.getAttribute("cart");
if (cart == null) {
cart = new Cart();
session.setAttribute("cart", cart);
}
// 業務:添加商品等
resp.getWriter().write("購物車物品: " + cart.size());
}
}
// Spring Session自動同步Redis
核心代碼片段3:CSRF防護Filter
@WebFilter("/*")
public class CsrfFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession();
if ("POST".equals(req.getMethod())) {
String token = req.getParameter("csrf");
if (token == null || !token.equals(session.getAttribute("csrf"))) {
((HttpServletResponse) response).sendError(403, "CSRF無效");
return;
}
} else {
String csrf = UUID.randomUUID().toString();
session.setAttribute("csrf", csrf);
req.setAttribute("csrf", csrf); // JSP使用
}
chain.doFilter(request, response);
}
}
核心代碼片段4:Session監聽在線統計
@WebListener
public class OnlineListener implements HttpSessionListener {
private static final AtomicInteger online = new AtomicInteger(0);
@Override
public void sessionCreated(HttpSessionEvent se) {
online.incrementAndGet();
broadcast(online.get());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
online.decrementAndGet();
broadcast(online.get());
}
private void broadcast(int count) {
// WebSocket或Redis Pub/Sub推送
}
}
項目部署:Docker Compose (Tomcat + Redis),Kubernetes StatefulSet。壓測:JMeter 50k登錄,Session一致性100%。