1. Cookie 基础概念
1.1 什么是 Cookie?
想象一下你去一家超市购物。超市给你一张会员卡(Cookie),上面记录了一个唯一的号码(如 session_id=abc123)。下次你再去同一家超市(网站),出示这张卡(浏览器自动发送 Cookie),超市(服务器)就能认出你(识别用户状态),知道你上次买了什么(个性化推荐),或者直接让你进入会员区(保持登录状态)。
技术定义: Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据。浏览器会存储它,并在下一次向同一服务器发起请求时携带上它。Cookie 使得服务器能够识别请求是否来自同一个浏览器,从而在无状态的 HTTP 协议上实现状态管理。
1.2 Cookie 的作用:为什么需要它?
HTTP 协议本身是无状态的。这意味着服务器处理每个请求时,它'不记得'之前处理过哪些请求。这就像每次你去银行柜台办事,柜员都把你当作第一次见面的陌生人。这显然不行!
Cookie 的主要作用就是解决这个问题,实现:
- 会话管理(Session Management): 用户登录状态、购物车内容等。服务器通常会生成一个唯一的 Session ID,通过 Cookie 发送给浏览器存储。浏览器后续请求携带此 ID,服务器就能找到对应的 Session 数据(存储在服务器端)。
- 个性化(Personalization): 用户偏好设置(如语言、主题)、记住用户名(非密码)。
- 跟踪(Tracking): 分析用户行为(广告、分析)。注意:这部分涉及隐私问题,需谨慎处理并遵守相关法规。
1.3 Cookie 的工作原理(通俗解释)
- 首次请求: 用户访问网站
example.com。
- 服务器响应: 服务器处理请求后,在 HTTP 响应头中加入
Set-Cookie 指令,例如 Set-Cookie: user_id=12345; Max-Age=3600; Path=/; HttpOnly。
- 浏览器存储: 用户的浏览器接收到响应,会根据指令将 Cookie(
user_id=12345)及其属性(有效期 1 小时、作用于整个站点、仅 HTTP 传输)存储在本地(通常是硬盘上的特定文件)。
- 后续请求: 当用户再次访问
example.com 或其子路径(符合 Path 设置)时,浏览器会自动在 HTTP 请求头中加入 Cookie: user_id=12345。
- 服务器识别: 服务器收到请求,读取
Cookie 头,就能知道这个请求来自用户 12345,从而提供个性化的服务或维持登录状态。
1.4 Cookie 的属性
Cookie 不仅仅是键值对,它还有一些重要的属性控制其行为:
- 名称(Name)和值(Value): 核心数据。例如
username=JohnDoe。
- 有效期(Expires / Max-Age): 决定 Cookie 的生命周期。
Expires: 指定一个具体的过期时间(GMT 格式)。
Max-Age: 指定从设置时刻起多少秒后过期。优先使用 Max-Age。
- 如果都不设置,Cookie 是会话 Cookie,只在浏览器打开期间有效,关闭浏览器即删除。
- 作用域(Domain): 指定哪些域名可以接收此 Cookie。例如
.example.com 表示 example.com 及其所有子域名(如 www.example.com, shop.example.com)都可以接收和发送此 Cookie。不设置则默认为当前文档的域名(不包括子域名)。
- 路径(Path): 指定 URL 路径前缀,只有匹配该路径的请求才会携带 Cookie。例如
/shop 表示只有访问 /shop 及其子路径(如 /shop/cart, /shop/checkout)时才会发送该 Cookie。默认为 /,即整个站点。
- Secure: 标记为
Secure 的 Cookie 只会在使用 HTTPS 协议加密的请求中发送给服务器。防止 Cookie 在非加密的 HTTP 连接中被窃听。
- HttpOnly: 标记为
HttpOnly 的 Cookie 不能被客户端的 JavaScript 脚本访问(例如 document.cookie)。这是重要的安全措施,有助于缓解跨站脚本攻击(XSS)窃取 Cookie。
- SameSite: 控制 Cookie 在跨站点请求时是否被发送。是防御**跨站请求伪造(CSRF)**攻击的关键。
Strict: 最严格,仅在同站点请求(请求 URL 与当前页面 URL 完全匹配)时发送。
Lax: (现代浏览器默认值)允许在顶级导航(例如点击链接)且使用安全方法(GET)的跨站点请求中发送。阻止在跨站 POST 提交或通过 <img>, <iframe> 加载资源时发送。
None: 允许在跨站点请求中发送,但必须同时设置 Secure 属性(即只允许在 HTTPS 下发送)。这是旧版行为,安全性最低,仅在特定跨域场景下需要。
2. Java 中的 Cookie 操作 (Servlet API)
Java EE 的 Servlet API 提供了 javax.servlet.http.Cookie 类来操作 Cookie。
2.1 创建 Cookie
import javax.servlet.http.Cookie;
Cookie usernameCookie = new Cookie("username", "JohnDoe");
Cookie sessionCookie = new Cookie("sessionId", java.util.UUID.randomUUID().toString());
2.2 将 Cookie 发送给客户端 (添加到响应)
import javax.servlet.http.HttpServletResponse;
response.addCookie(usernameCookie);
2.3 从客户端读取 Cookie (从请求中获取)
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Cookie;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("username".equals(cookie.getName())) {
String usernameValue = cookie.getValue();
break;
}
}
}
2.4 修改 Cookie
修改 Cookie 实际上是在服务器端创建一个同名的新 Cookie,设置新的值或属性,然后通过 response.addCookie() 发送给浏览器。浏览器会用新的覆盖旧的。
Cookie existingCookie = ...;
existingCookie.setValue("NewValue");
existingCookie.setMaxAge(60 * 60 * 24 * 7);
response.addCookie(existingCookie);
2.5 删除 Cookie
删除 Cookie 也是通过创建一个同名的新 Cookie 来实现:
- 设置
setMaxAge(0) - 立即过期。
- (可选但推荐)设置
setPath() 和 setDomain() 与要删除的 Cookie 创建时的设置完全一致。如果不一致,浏览器可能会创建一个同名但路径/域不同的新无效 Cookie,而旧的 Cookie 依然存在。
- 通过
response.addCookie() 发送。
Cookie deleteCookie = new Cookie("username", "");
deleteCookie.setMaxAge(0);
deleteCookie.setPath("/");
response.addCookie(deleteCookie);
2.6 设置 Cookie 的有效期 (setMaxAge)
Cookie cookie = new Cookie("rememberMe", "yes");
cookie.setMaxAge(60 * 60 * 24 * 30);
response.addCookie(cookie);
2.7 设置 Cookie 的路径 (setPath)
Cookie cookie = new Cookie("cartId", "cart789");
cookie.setPath("/shop");
response.addCookie(cookie);
2.8 设置 Cookie 的域 (setDomain)
Cookie cookie = new Cookie("preferences", "dark_theme");
cookie.setDomain(".example.com");
response.addCookie(cookie);
2.9 安全相关属性 (setHttpOnly, setSecure)
Cookie cookie = new Cookie("authToken", "encryptedTokenValue");
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
cookie.setMaxAge(60 * 60);
response.addCookie(cookie);
3. Spring Boot 中的 Cookie 操作
Spring Boot 构建在 Servlet API 之上,因此你可以直接使用 HttpServletResponse 和 HttpServletRequest 来操作 Cookie,方法和第 2 节完全一样。此外,Spring 提供了更便捷的方式。
3.1 使用 HttpServletResponse (与传统 Servlet 类似)
在 Spring MVC 的 Controller 方法中,你可以注入 HttpServletResponse。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
@RestController
public class CookieController {
@GetMapping("/setcookie")
public String setCookie(HttpServletResponse response) {
Cookie cookie = new Cookie("user", "Alice");
cookie.setMaxAge(7 * 24 * 60 * 60);
cookie.setPath("/");
cookie.setHttpOnly(true);
response.addCookie(cookie);
return "Cookie has been set!";
}
}
3.2 使用 @CookieValue 注解 (便捷获取)
Spring MVC 提供了 @CookieValue 注解,可以直接将请求中的 Cookie 值绑定到 Controller 方法的参数上。
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ReadCookieController {
@GetMapping("/getcookie")
public String readCookie(@CookieValue(name = "user", defaultValue = "Guest") String username) {
return "Hello, " + username + "! (Read from cookie)";
}
}
注意:@CookieValue 要求 Cookie 必须存在(除非设置了 defaultValue)。如果 Cookie 可能不存在且你不希望抛出异常,可以将其绑定为 java.util.Optional:
@GetMapping("/getcookieopt")
public String readCookieOptional(@CookieValue(name = "user") Optional<String> username) {
return "Hello, " + username.orElse("Guest") + "!";
}
3.3 使用 ResponseCookie (Spring Boot 提供的更现代的构建器)
从 Spring Boot 2.5 (Spring Framework 5.1) 开始,引入了 org.springframework.http.ResponseCookie 类。它提供了流畅的构建器 API 来创建 Cookie,并且生成的字符串可以直接添加到 Response 的 Set-Cookie 头。这种方式更符合 HTTP 头的语义。
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ResponseCookieController {
@GetMapping("/setresponsecookie")
public ResponseEntity<String> setResponseCookie() {
ResponseCookie responseCookie = ResponseCookie.from("theme", "dark")
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(60 * 60 * 24)
.domain("example.com")
.sameSite("Lax")
.build();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, responseCookie.toString())
.body("ResponseCookie has been set!");
}
}
优点:
- 流畅的 API,设置属性更清晰。
- 直接生成符合
Set-Cookie 头规范的字符串。
- 更容易设置
SameSite 等属性。
- 可以添加多个
Set-Cookie 头(多次调用 .header())。
4. Cookie 的安全性与最佳实践
Cookie 经常用于存储会话标识符或认证令牌,因此安全性至关重要。
4.1 HttpOnly Cookie (防御 XSS)
- 为什么: 阻止恶意 JavaScript (
document.cookie) 窃取敏感的 Cookie(如 Session ID)。
- 怎么做: 在服务器端设置 Cookie 时,务必设置
setHttpOnly(true) 或使用 ResponseCookie 的 .httpOnly(true)。
4.2 Secure Cookie (防御嗅探)
- 为什么: 防止 Cookie 在未加密的 HTTP 连接中被网络窃听者截获。
- 怎么做: 在服务器端设置 Cookie 时,如果你的网站启用了 HTTPS,为包含敏感信息(特别是会话标识符)的 Cookie 设置
setSecure(true) 或 .secure(true)。注意: 在本地开发环境 (HTTP) 下设置 Secure 会导致浏览器不存储该 Cookie。
4.3 SameSite 属性 (防御 CSRF)
- 为什么: 阻止攻击者利用用户的登录状态(浏览器中的 Cookie)在用户不知情的情况下发起恶意请求(如转账)。
- 怎么做: 强烈建议为会话 Cookie 设置
SameSite=Lax(现代浏览器默认值)或 SameSite=Strict。SameSite=None 仅在需要跨域携带 Cookie 的场景下使用(如 OAuth 登录回调、跨域 AJAX 请求),并且必须配合 Secure 属性(即仅限 HTTPS)。使用 ResponseCookie 可以方便地设置 .sameSite("Lax")。
4.4 签名与加密 (防止篡改)
- 为什么: 防止客户端篡改 Cookie 的值(例如,用户试图将自己的普通权限 Cookie 改为管理员权限)。
- 怎么做:
- 签名 (Signing): 服务器在设置 Cookie 值时,附加一个基于该值和服务器密钥生成的签名(如 HMAC)。在读取 Cookie 时,服务器重新计算签名并与 Cookie 中的签名对比。如果签名不匹配,说明值被篡改,应拒绝该 Cookie。Spring 提供了
org.springframework.web.util.WebUtils 的 getCookie 和 addCookie 方法,以及更强大的 org.springframework.session.web.http.CookieSerializer 来处理签名 Cookie(Spring Session 模块常用)。
- 加密 (Encryption): 直接对 Cookie 的值进行加密(如 AES),只有服务器持有密钥才能解密。这比签名更安全,但也更耗费资源。通常结合使用(先签名再加密或反之)。
4.5 敏感信息不要存储在 Cookie 中
- 为什么: Cookie 存储在用户浏览器上,可以被用户查看(即使 HttpOnly 也只能阻止 JavaScript),也可能被其他漏洞利用。网络传输中即使有 HTTPS 和 Secure Cookie,也存在潜在风险。
- 怎么做: 不要在 Cookie 中直接存储用户的密码、信用卡号、身份证号等敏感信息。通常只存储一个随机生成的、无意义的标识符(如 Session ID、Token),服务器端通过这个标识符去关联存储在数据库或缓存中的真实用户数据。
4.6 Cookie 大小限制
- 每个 Cookie 的大小通常限制在 4KB 左右(不同浏览器可能有细微差异)。
- 每个域名下的 Cookie 总数也有限制(通常是 50 个左右)。
- 怎么做: 避免在 Cookie 中存储大量数据。如果需要存储大量用户状态,考虑使用服务器端 Session 存储(数据库、缓存),Cookie 仅存储 Session ID。
5. 实战案例:可直接运行的 Spring Boot 示例
以下是几个使用 Spring Boot 3.x 的完整、可运行的案例。确保你的项目包含 Spring Boot Web Starter 依赖。
案例 1:基础读写 - 记录用户访问次数
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
@RestController
public class VisitCounterController {
@GetMapping("/visit")
public ResponseEntity<String> trackVisit(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
int visitCount = 0;
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("visitCount".equals(cookie.getName())) {
try {
visitCount = Integer.parseInt(cookie.getValue());
} catch (NumberFormatException e) {
visitCount = 0;
}
break;
}
}
}
visitCount++;
Cookie visitCookie = new Cookie("visitCount", String.valueOf(visitCount));
visitCookie.setMaxAge(365 * 24 * * );
visitCookie.setPath();
visitCookie.setHttpOnly();
response.addCookie(visitCookie);
ResponseEntity.ok( + visitCount + );
}
}
测试: 访问 http://localhost:8080/visit,刷新页面观察计数增加。查看浏览器开发者工具(Application -> Cookies)可以看到 visitCount Cookie。
案例 2:安全设置 - 登录认证 Token (HttpOnly, Secure)
假设你有一个登录服务,登录成功后生成一个认证 Token。
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LoginController {
@PostMapping("/login")
public ResponseEntity<String> login(
@RequestParam String username,
@RequestParam String password,
HttpServletResponse response) {
if ("admin".equals(username) && "secret".equals(password)) {
String authToken = generateSecureToken();
ResponseCookie authCookie = ResponseCookie.from("authToken", authToken)
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(30 * 60)
.sameSite()
.build();
ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, authCookie.toString())
.body( + username + );
} {
ResponseEntity.status().body();
}
}
ResponseEntity<String> {
ResponseCookie.from(, )
.httpOnly()
.secure()
.path()
.maxAge()
.sameSite()
.build();
ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, deleteCookie.toString())
.body();
}
String {
+ System.currentTimeMillis();
}
}
测试:
- 使用 Postman 或 curl 向
POST http://localhost:8080/login 发送表单数据 (username=admin&password=secret)。检查响应头 Set-Cookie,应包含 authToken 且带有 HttpOnly; Secure; SameSite=Lax 属性(本地开发注释 .secure(true) 后则没有 Secure)。
- 访问其他需要认证的端点(假设你有),浏览器会在请求头
Cookie 中自动带上 authToken。
- 访问
GET http://localhost:8080/logout,响应头 Set-Cookie 会设置一个过期时间为 0 的 authToken Cookie,浏览器收到后会删除它。
案例 3:跨域/子域共享 Cookie (Domain, Path)
假设你有主站 www.example.com 和一个商城子站 shop.example.com,你想在主站登录后,用户在访问商城时也能保持登录状态。
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SharedCookieController {
@GetMapping("/setsharedcookie")
public ResponseEntity<String> setSharedCookie() {
String sharedValue = "SHARED_VALUE_123";
ResponseCookie sharedCookie = ResponseCookie.from("sharedData", sharedValue)
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(24 * 60 * 60)
.domain(".example.com")
.sameSite("Lax")
.build();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, sharedCookie.toString())
.body("Shared cookie has been set for .example.com!");
}
@GetMapping("/readsharedcookie")
public ResponseEntity<String> readSharedCookie String sharedValue) {
(sharedValue != && !sharedValue.isEmpty()) {
ResponseEntity.ok( + sharedValue);
} {
ResponseEntity.ok();
}
}
}
重要说明:
- 此代码需要在
www.example.com 或 shop.example.com 或其他 *.example.com 子域下运行。
- 当你在
www.example.com/setsharedcookie 设置 Cookie 后,访问 shop.example.com/readsharedcookie,浏览器会因为设置了 Domain=.example.com 和 Path=/,自动在请求中包含这个 Cookie。
- 本地测试: 要测试跨子域,你需要修改本地
hosts 文件 (如 /etc/hosts 或 C:\Windows\System32\drivers\etc\hosts),添加条目如 127.0.0.1 www.localdev.com 和 127.0.0.1 shop.localdev.com。然后启动 Spring Boot 应用,分别访问 http://www.localdev.com:8080/setsharedcookie 和 http://shop.localdev.com:8080/readsharedcookie。注意:非 HTTPS 环境下,Secure 属性会阻止 Cookie 存储和发送。
案例 4:用户登录状态管理
本案例实现基于 Cookie 的用户登录状态管理。用户登录后,服务器设置 Cookie 存储 session ID;后续请求验证 Cookie 以维持登录状态。
完整代码
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
if ("admin".equals(username) && "password123".equals(password)) {
Cookie sessionCookie = new Cookie("sessionID", "generatedSession123");
sessionCookie.setMaxAge(60 * 60 * 24);
sessionCookie.setPath();
sessionCookie.setHttpOnly();
sessionCookie.setSecure();
response.addCookie(sessionCookie);
out.println();
out.println( + username + );
out.println();
out.println();
} {
out.println();
out.println();
out.println();
}
}
}
{
ServletException, IOException {
response.setContentType();
response.getWriter();
Cookie[] cookies = request.getCookies();
;
(cookies != ) {
(Cookie cookie : cookies) {
(.equals(cookie.getName())) {
sessionID = cookie.getValue();
;
}
}
}
(sessionID != && sessionID.equals()) {
out.println();
out.println();
out.println();
out.println();
} {
response.sendRedirect();
}
}
}
{
ServletException, IOException {
(, );
sessionCookie.setMaxAge();
sessionCookie.setPath();
response.addCookie(sessionCookie);
response.sendRedirect();
}
}
使用说明
- 部署应用,访问
http://localhost:8080/yourApp/login.html。
- 登录后,访问
/profile 验证状态;退出使用 /logout。
创建 login.html 表单:
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<form action="login" method="post">
Username: <input type="text" name="username"><br>
Password: <input type="password" name="password"><br>
<input type="submit" value="Login">
</form>
</body>
</html>
案例 5:购物车实现
本案例使用 Cookie 实现简单购物车。用户添加商品时,服务器更新 Cookie 存储商品 ID 列表。
完整代码
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/addToCart")
public class AddToCartServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String productId = request.getParameter("id");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
Cookie[] cookies = request.getCookies();
String cartValue = "";
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("shoppingCart".equals(cookie.getName())) {
cartValue = cookie.getValue();
break;
}
}
}
if (!cartValue.isEmpty()) {
cartValue += + productId;
} {
cartValue = productId;
}
(, cartValue);
cartCookie.setMaxAge( * * * );
cartCookie.setPath();
cartCookie.setHttpOnly();
response.addCookie(cartCookie);
out.println();
out.println( + productId + );
out.println();
out.println();
}
}
{
ServletException, IOException {
response.setContentType();
response.getWriter();
Cookie[] cookies = request.getCookies();
;
(cookies != ) {
(Cookie cookie : cookies) {
(.equals(cookie.getName())) {
cartValue = cookie.getValue();
;
}
}
}
out.println();
out.println();
(!cartValue.isEmpty()) {
List<String> products = Arrays.asList(cartValue.split());
out.println();
(String product : products) {
out.println( + product + );
}
out.println();
} {
out.println();
}
out.println();
}
}
{
ServletException, IOException {
(, );
cartCookie.setMaxAge();
cartCookie.setPath();
response.addCookie(cartCookie);
response.sendRedirect();
}
}
使用说明
- 添加商品链接:
<a href="/yourApp/addToCart?id=1">Add Product 1</a>。
- 访问
/viewCart 查看购物车;/clearCart 清空。
- 注意:实际应用中,商品 ID 应来自数据库,Cookie 值需 URL 编码处理特殊字符。
案例 6:记住我功能
本案例实现'记住我'功能:用户登录时选择记住,服务器设置长期 Cookie 存储用户名(加密),下次自动填充。
完整代码
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.util.Base64;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/rememberMeLogin")
public class RememberMeLoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
String rememberMe = request.getParameter("rememberMe");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
if ("admin".equals(username) && "password123".equals(password)) {
Cookie sessionCookie (, );
sessionCookie.setMaxAge( * );
sessionCookie.setPath();
sessionCookie.setHttpOnly();
response.addCookie(sessionCookie);
(.equals(rememberMe)) {
Base64.getEncoder().encodeToString(username.getBytes());
(, encodedUsername);
rememberCookie.setMaxAge( * * * );
rememberCookie.setPath();
rememberCookie.setHttpOnly();
rememberCookie.setSecure();
response.addCookie(rememberCookie);
}
out.println();
out.println();
out.println();
} {
out.println();
out.println();
out.println();
}
}
}
{
ServletException, IOException {
response.setContentType();
response.getWriter();
Cookie[] cookies = request.getCookies();
;
(cookies != ) {
(Cookie cookie : cookies) {
(.equals(cookie.getName())) {
encodedUsername = cookie.getValue();
;
}
}
}
(encodedUsername != ) {
(Base64.getDecoder().decode(encodedUsername));
(, );
sessionCookie.setMaxAge( * );
sessionCookie.setPath();
sessionCookie.setHttpOnly();
response.addCookie(sessionCookie);
out.println();
out.println( + username + );
out.println();
} {
response.sendRedirect();
}
}
}
使用说明
- 访问
/autoLogin 尝试自动登录。
- 注意:实际应用中,应使用强加密(如 AES)存储用户名,并考虑安全风险。
登录表单添加复选框:
<input type="checkbox" name="rememberMe"> Remember me
6. 高级主题与注意事项
6.1 Cookie 与 Session 的关系
- Cookie: 通常用于在客户端存储一个小的标识符(Session ID)。
- Session: 指在服务器端存储的用户相关数据(如登录状态、购物车)。Session 数据存储在服务器内存、数据库或缓存(如 Redis)中。
- 关系: 服务器在用户首次访问时创建一个唯一的 Session ID,通过 Cookie (
JSESSIONID 是 Java EE 默认名) 发送给浏览器。浏览器后续请求携带此 ID,服务器通过 ID 查找对应的 Session 数据。因此,Cookie 是实现 Session 持久化的一种常用机制。
6.2 Cookie 的替代方案
- Token-Based Authentication (JWT etc.): 在认证成功后,服务器生成一个签名的 Token(包含用户信息、过期时间等),通常通过响应体(JSON)返回给客户端。客户端(通常是 Web 前端)将其存储在
localStorage 或 sessionStorage 中,并在后续请求的 Authorization 头(如 Bearer <token>)中发送。这种方式更符合 RESTful 无状态设计,避免了 CSRF 问题(因为 Cookie 不是自动发送),但需要防范 XSS 窃取存储的 Token。
- LocalStorage / SessionStorage: HTML5 提供的浏览器存储机制,容量更大(通常 5MB),数据不会随 HTTP 请求自动发送。适合存储非敏感的应用状态数据(如用户偏好)。同样需要防范 XSS。
6.3 浏览器限制与隐私政策 (GDPR, CCPA)
- 浏览器限制: 如前所述,有大小和数量限制。第三方 Cookie(Domain 与当前页面 URL 不同的 Cookie)在现代浏览器中受到越来越严格的限制(如 Safari 默认完全阻止,Chrome 计划逐步淘汰)。
- 隐私法规:
- GDPR (欧盟通用数据保护条例): 要求网站在使用非必要的 Cookie(特别是用于跟踪的)之前,必须获得用户的明确同意(通过 Cookie 横幅)。必要的 Cookie(如会话管理、安全)通常不需要同意。
- CCPA (加州消费者隐私法案): 类似地,赋予了用户选择不出售其个人信息的权利,这会影响跟踪 Cookie 的使用。
- 怎么做: 部署 Cookie 横幅/同意管理平台 (CMP),允许用户管理其 Cookie 偏好。仔细审查你的 Cookie 使用,区分必要和非必要。
6.4 在 RESTful API 中使用 Cookie
虽然 RESTful API 通常倾向于无状态并使用 Token 认证,但有时也需要使用 Cookie(例如,在服务端渲染的 Web 应用提供的 API 端点)。
- 支持 Cookie: 客户端(浏览器、支持 Cookie 的 HTTP 客户端库)需要在请求中自动包含 Cookie。
- 跨域问题 (CORS): 如果 API 和前端处于不同域名,浏览器默认不会发送 Cookie。需要在服务器端设置 CORS 响应头:
Access-Control-Allow-Origin: <具体前端域名> (不能是 *,需要明确指定)
Access-Control-Allow-Credentials: true
- 前端在发起请求时(如使用
fetch)需要设置 credentials: 'include'。
- 安全考虑: 务必设置
HttpOnly, Secure, SameSite 等属性,并考虑签名/加密。
7. 总结
Cookie 是 Web 开发中管理状态的基础机制。掌握 Java (Servlet API) 和 Spring Boot 中操作 Cookie 的方法至关重要。在实现功能的同时,必须将安全性放在首位:使用 HttpOnly 防御 XSS,使用 Secure 防御嗅探(HTTPS 下),使用 SameSite (通常 Lax) 防御 CSRF。避免在 Cookie 中存储敏感信息,考虑使用服务器端 Session 存储。注意隐私法规要求(GDPR/CCPA),提供 Cookie 同意管理。理解 Cookie 在会话管理中的作用以及与替代方案(Token、LocalStorage)的区别和适用场景。
通过本文的详细解释和可直接运行的 Spring Boot 案例,你应该能够自信地在你的 Java Web 应用中安全有效地使用 Cookie 了。