Cookie 是伺服器儲存在客戶端瀏覽器的小型資料片段(Key-Value),用於識別用戶狀態。HTTP 無狀態特性下,Cookie 成為會話追蹤的核心機制。
setMaxAge(秒) 設定持久化setPath() 控制路徑範圍,setDomain() 控制跨子域共享Secure(僅 HTTPS 傳輸)、HttpOnly(防 XSS 竊取)、SameSite(防 CSRF)@WebServlet("/cookie-demo")
public class CookieDemoServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1. 建立 Cookie
Cookie cookie = new Cookie("username", "john_doe");
cookie.setMaxAge(30 * 60); // 30 分鐘有效
cookie.setPath(req.getContextPath()); // 應用根路徑
cookie.setHttpOnly(true); // 防 JavaScript 訪問
cookie.setSecure(true); // 僅 HTTPS(生產環境必開)
// 2. 寫入回應頭
resp.addCookie(cookie);
resp.getWriter().write("Cookie 已寫入");
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if ("username".equals(c.getName())) {
resp.getWriter().write("歡迎回來," + c.getValue());
return;
}
}
}
resp.getWriter().write("首次訪問或 Cookie 已過期");
}
流程:登入成功 → 建立持久化 Cookie → 後續訪問自動識別
// LoginServlet.java - 登入處理
String username = req.getParameter("username");
String password = req.getParameter("password");
boolean remember = "on".equals(req.getParameter("remember"));
if (userService.authenticate(username, password)) {
// 登入成功,存入 Session
req.getSession().setAttribute("user", username);
// 記住我:寫入加密 Token Cookie(實際應用應用 JWT 或簽名 Token)
if (remember) {
String token = tokenService.generatePersistentToken(username);
Cookie cookie = new Cookie("auto_login_token", token);
cookie.setMaxAge(30 * 24 * 3600); // 30 天
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setSecure(true);
resp.addCookie(cookie);
}
resp.sendRedirect("/dashboard");
}
// AutoLoginFilter.java - 自動登入過濾器(攔截所有請求)
public class AutoLoginFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 若 Session 無用戶,嘗試從 Cookie 恢復
if (req.getSession().getAttribute("user") == null) {
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if ("auto_login_token".equals(c.getName())) {
String username = tokenService.validateAndGetUser(c.getValue());
if (username != null) {
req.getSession().setAttribute("user", username);
break;
}
}
}
}
}
chain.doFilter(request, response);
}
}
利用 Cookie 儲存最近瀏覽的商品 ID 列表,最多保留 5 筆,採用 LRU 策略。
// BookDetailServlet.java - 商品詳情頁
String bookId = req.getParameter("id");
Cookie[] cookies = req.getCookies();
List<String> recentIds = new ArrayList<>();
// 1. 讀取現有記錄
if (cookies != null) {
for (Cookie c : cookies) {
if (c.getName().startsWith("recent_book_")) {
recentIds.add(c.getValue());
}
}
}
// 2. 更新 LRU 列表
recentIds.remove(bookId); // 移除舊位置
recentIds.add(0, bookId); // 加到最前
if (recentIds.size() > 5) recentIds.remove(5); // 截斷
// 3. 重寫所有 Cookie(簡化:刪除舊的、寫入新的)
for (Cookie c : cookies) {
if (c.getName().startsWith("recent_book_")) {
c.setMaxAge(0); resp.addCookie(c); // 刪除
}
}
for (int i = 0; i < recentIds.size(); i++) {
Cookie c = new Cookie("recent_book_" + i, recentIds.get(i));
c.setMaxAge(30 * 24 * 3600);
c.setPath("/");
resp.addCookie(c);
}
setPath() 決定 Cookie 隨請求發送的 URL 前綴匹配規則。
// 僅在 /admin/* 路徑下發送
Cookie adminCookie = new Cookie("admin_panel", "prefs");
adminCookie.setPath(req.getContextPath() + "/admin");
resp.addCookie(adminCookie);
// 全站可見(預設行為)
Cookie siteCookie = new Cookie("theme", "dark");
siteCookie.setPath(req.getContextPath()); // 或 "/"
resp.addCookie(siteCookie);
規則:瀏覽器請求 /admin/dashboard 會攜帶兩個 Cookie;請求 /home 僅攜帶 theme。
Path、Domain 盡量縮小範圍| 現象 | 可能原因 | 解決方向 |
|---|---|---|
| Cookie 無法寫入 | Domain/Path 不匹配、Secure 模式下 HTTP 環境 | 檢查 setPath、setDomain、協議匹配 |
| 跨子域不共享 | 未設定父域名 | cookie.setDomain(".example.com")(前綴點) |
| 中文亂碼 | Cookie 標準僅支援 ASCII | URLEncoder.encode / URLDecoder.decode |
| 大小量超限 | 單 Cookie ≤4KB、單域 ≤20-50 個 | 精簡資料、改用 Session/本地儲存 |