Cookie 是 HTTP 無狀態協議中維持客戶端狀態的核心機制。Java Servlet API 提供 javax.servlet.http.Cookie 類別進行操作。本文涵蓋**寫入、讀取、屬性設定、安全強化、常見坑**與**工具類封裝**。
| 方法 | 說明 |
|---|---|
new Cookie(name, value) | 建立 Cookie,名值皆為 String |
setMaxAge(int seconds) | 存活秒數:>0 持久化;=0 刪除;<0 會話級(瀏覽器關閉失效) |
setPath(String) | 生效路徑,預設為當前 Servlet 路徑,**建議設為 "/"** |
setDomain(String) | 生效網域,支援子網域共享(如 .example.com) |
setSecure(boolean) | 僅 HTTPS 傳輸 |
setHttpOnly(boolean) | 防止 JavaScript 讀取(XSS 防護),**強烈建議開啟** |
response.addCookie(cookie) | 寫入響應標頭 Set-Cookie |
request.getCookies() | 讀取請求標頭 Cookie,回傳 Cookie[] 或 null |
/**
* 寫入加密的使用者識別 Cookie
*/
public void writeUserCookies(HttpServletResponse response, String schoolId, String userId, String encodedUsername) {
// 1. 使用者名稱 Cookie(一年有效期)
Cookie usernameCookie = new Cookie("username_" + schoolId, encodedUsername);
usernameCookie.setMaxAge(60 * 60 * 24 * 365); // 1 年
usernameCookie.setPath("/"); // 全站有效
usernameCookie.setHttpOnly(true); // 防 XSS
usernameCookie.setSecure(true); // 僅 HTTPS(生產環境必開)
// usernameCookie.setDomain(".example.com"); // 跨子網域共享時啟用
response.addCookie(usernameCookie);
// 2. 使用者 ID Cookie
Cookie userIdCookie = new Cookie("userId_" + schoolId, userId);
userIdCookie.setMaxAge(60 * 60 * 24 * 365);
userIdCookie.setPath("/");
userIdCookie.setHttpOnly(true);
userIdCookie.setSecure(true);
response.addCookie(userIdCookie);
}
/**
* 讀取指定前綴的 Cookie 值
* @param request HttpServletRequest
* @param cookieNamePrefix Cookie 名稱前綴(如 "userId_")
* @param schoolId 業務識別碼
* @return Cookie 值或 null
*/
public String readCookieValue(HttpServletRequest request, String cookieNamePrefix, String schoolId) {
Cookie[] cookies = request.getCookies();
if (cookies == null) return null;
String targetName = cookieNamePrefix + schoolId;
for (Cookie cookie : cookies) {
if (targetName.equals(cookie.getName())) {
return cookie.getValue();
}
}
return null;
}
// 使用範例
String userId = readCookieValue(request, "userId_", schoolId);
package com.utils.web;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Optional;
public final class CookieUtils {
private CookieUtils() {}
/**
* 新增/更新 Cookie(預設安全屬性)
*/
public static void addCookie(HttpServletResponse response, String name, String value, int maxAgeSeconds) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setMaxAge(maxAgeSeconds);
cookie.setHttpOnly(true);
cookie.setSecure(true); // 僅 HTTPS,開發環境可透過配置關閉
response.addCookie(cookie);
}
/**
* 刪除 Cookie(設 maxAge=0)
*/
public static void removeCookie(HttpServletResponse response, String name) {
Cookie cookie = new Cookie(name, "");
cookie.setPath("/");
cookie.setMaxAge(0);
cookie.setHttpOnly(true);
cookie.setSecure(true);
response.addCookie(cookie);
}
/**
* 讀取 Cookie 值(Optional 避免 NPE)
*/
public static Optional getCookieValue(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
if (cookies == null) return Optional.empty();
return Arrays.stream(cookies)
.filter(c -> name.equals(c.getName()))
.map(Cookie::getValue)
.findFirst();
}
/**
* 讀取所有 Cookie 轉 Map
*/
public static java.util.Map getAllCookies(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies == null) return java.util.Collections.emptyMap();
return Arrays.stream(cookies)
.collect(java.util.stream.Collectors.toMap(
Cookie::getName,
Cookie::getValue,
(v1, v2) -> v1 // 重複名稱取第一個
));
}
}
| 屬性 | 作用 | 建議設定 | 風險 |
|---|---|---|---|
HttpOnly | 禁止 JS 存取 document.cookie | 必開 true | XSS 攻擊竊取 Session ID |
Secure | 僅 HTTPS 傳輸 | 生產必開 true,開發可配置關閉 | 中間人攻擊竊取 Cookie |
SameSite | 控制跨站請求攜帶 Cookie | Strict 或 Lax(Servlet API 需手動加標頭) | CSRF 攻擊 |
Path | 限制生效路徑 | /(全站)或具體應用路徑 | 路徑洩漏導致 Cookie 被非預期頁面送出 |
Domain | 限制生效網域 | 不設(僅當前網域)或 .example.com(共享子網域) | 網域過寬導致 Cookie 被惡意子網域竊取 |
// 手動構建 Set-Cookie 標頭加入 SameSite
String cookieHeader = String.format("%s=%s; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=%d",
name, value, maxAge);
response.addHeader("Set-Cookie", cookieHeader);
request.getCookies() 回傳 null:客戶端未攜帶任何 Cookie 時回傳 null,非空陣列。**必須判空**。Cookie.getName() 比對時**嚴格區分大小寫**。Name + Domain + Path 為鍵,後寫入者覆蓋先寫入者。URLEncoder.encode(value, "UTF-8") 寫入,讀取時 URLDecoder.decode(value, "UTF-8")。setDomain(".example.com") 可讓 a.example.com、b.example.com 共享,**需確保信任所有子網域**。Java Servlet 操作 Cookie 雖 API 簡單,但**安全屬性配置(HttpOnly、Secure、SameSite)直接關係應用安全性**。建議: