跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Java Cookie 全面指南:原理与 Spring Boot 实战

综述由AI生成Java Cookie 的基础概念、工作原理及属性。涵盖了 Servlet API 操作 Cookie 的方法,以及 Spring Boot 中通过 HttpServletResponse、@CookieValue 和 ResponseCookie 的三种实现方式。重点讲解了 HttpOnly、Secure、SameSite 等安全属性配置,防止 XSS、嗅探和 CSRF 攻击。提供了登录认证、购物车管理、跨域共享及记住我功能的完整代码示例,并探讨了 Cookie 与 Session 的关系、替代方案及隐私合规要求,适合 Java Web 开发者参考。

月亮邮递员发布于 2026/3/30更新于 2026/5/2823 浏览
Java Cookie 全面指南:原理与 Spring Boot 实战

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 的工作原理(通俗解释)

  1. 首次请求: 用户访问网站 example.com。
  2. 服务器响应: 服务器处理请求后,在 HTTP 响应头中加入 Set-Cookie 指令,例如 Set-Cookie: user_id=12345; Max-Age=3600; Path=/; HttpOnly。
  3. 浏览器存储: 用户的浏览器接收到响应,会根据指令将 Cookie(user_id=12345)及其属性(有效期 1 小时、作用于整个站点、仅 HTTP 传输)存储在本地(通常是硬盘上的特定文件)。
  4. 后续请求: 当用户再次访问 example.com 或其子路径(符合 Path 设置)时,浏览器会自动在 HTTP 请求头中加入 Cookie: user_id=12345。
  5. 服务器识别: 服务器收到请求,读取 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;
    // 创建一个名为 "username",值为 "JohnDoe" 的 Cookie
    Cookie usernameCookie = new Cookie("username", "JohnDoe");
    // 创建一个名为 "sessionId",值为随机 UUID 的 Cookie (常用于会话管理)
    Cookie sessionCookie = new Cookie("sessionId", java.util.UUID.randomUUID().toString());
    

    2.2 将 Cookie 发送给客户端 (添加到响应)

    import javax.servlet.http.HttpServletResponse;
    // 假设 response 是 HttpServletResponse 对象
    response.addCookie(usernameCookie); // 将 cookie 添加到响应头
    

    2.3 从客户端读取 Cookie (从请求中获取)

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.Cookie;
    // 假设 request 是 HttpServletRequest 对象
    Cookie[] cookies = request.getCookies(); // 获取请求中的所有 Cookie
    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if ("username".equals(cookie.getName())) {
                String usernameValue = cookie.getValue(); // 处理 usernameValue...
                break;
            }
        }
    }
    

    2.4 修改 Cookie

    修改 Cookie 实际上是在服务器端创建一个同名的新 Cookie,设置新的值或属性,然后通过 response.addCookie() 发送给浏览器。浏览器会用新的覆盖旧的。

    // 假设我们找到了要修改的 cookie
    Cookie existingCookie = ...; // 从 request.getCookies() 中找出
    existingCookie.setValue("NewValue"); // 修改值
    existingCookie.setMaxAge(60 * 60 * 24 * 7); // 修改有效期为一周
    response.addCookie(existingCookie); // 发送更新后的 cookie
    

    2.5 删除 Cookie

    删除 Cookie 也是通过创建一个同名的新 Cookie 来实现:

    1. 设置 setMaxAge(0) - 立即过期。
    2. (可选但推荐)设置 setPath() 和 setDomain() 与要删除的 Cookie 创建时的设置完全一致。如果不一致,浏览器可能会创建一个同名但路径/域不同的新无效 Cookie,而旧的 Cookie 依然存在。
    3. 通过 response.addCookie() 发送。
    Cookie deleteCookie = new Cookie("username", ""); // 值不重要,设为空或任意
    deleteCookie.setMaxAge(0); // 告诉浏览器立即删除此 Cookie
    deleteCookie.setPath("/"); // 必须与原始 Cookie 设置的路径匹配!这里是 '/'
    // deleteCookie.setDomain("yourdomain.com"); // 如果原始设置了 Domain,这里也要设置
    response.addCookie(deleteCookie);
    

    2.6 设置 Cookie 的有效期 (setMaxAge)

    Cookie cookie = new Cookie("rememberMe", "yes");
    cookie.setMaxAge(60 * 60 * 24 * 30); // 设置有效期为 30 天 (单位:秒)
    response.addCookie(cookie);
    

    2.7 设置 Cookie 的路径 (setPath)

    Cookie cookie = new Cookie("cartId", "cart789");
    cookie.setPath("/shop"); // 只有访问 /shop 及其子路径时才会发送此 Cookie
    response.addCookie(cookie);
    

    2.8 设置 Cookie 的域 (setDomain)

    Cookie cookie = new Cookie("preferences", "dark_theme");
    cookie.setDomain(".example.com"); // 注意前面的点 '.',表示 example.com 及其所有子域
    response.addCookie(cookie);
    

    2.9 安全相关属性 (setHttpOnly, setSecure)

    Cookie cookie = new Cookie("authToken", "encryptedTokenValue");
    cookie.setHttpOnly(true); // 防止 XSS 攻击窃取
    cookie.setSecure(true); // 只在 HTTPS 连接中发送
    cookie.setPath("/");
    cookie.setMaxAge(60 * 60); // 1 小时有效
    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); // 7 days
            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 = ResponseCookie.from("theme", "dark")
                .httpOnly(true) // HttpOnly
                .secure(true) // Secure
                .path("/") // Path
                .maxAge(60 * 60 * 24) // Max-Age (1 天)
                .domain("example.com") // Domain
                .sameSite("Lax") // SameSite 属性 (Strict, Lax, None)
                .build(); // 构建
            // 响应,将 Cookie 添加到 Set-Cookie 头
            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) {
            // 1. 尝试从请求中获取名为 "visitCount" 的 Cookie
            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;
                    }
                }
            }
            // 2. 增加访问次数
            visitCount++;
            // 3. 创建或更新 Cookie,设置新值
            Cookie visitCookie = new Cookie("visitCount", String.valueOf(visitCount));
            visitCookie.setMaxAge(365 * 24 * 60 * 60); // 1 年有效期
            visitCookie.setPath("/"); // 作用于整个站点
            visitCookie.setHttpOnly(true); // 推荐设置
            response.addCookie(visitCookie);
            // 4. 返回响应
            return ResponseEntity.ok("Welcome back! This is your visit #" + 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) {
            // 1. 验证用户名密码 (这里简化为固定值)
            if ("admin".equals(username) && "secret".equals(password)) {
                // 2. 登录成功,生成一个安全的 Token (实际应用中应使用 JWT 或其他安全机制生成)
                String authToken = generateSecureToken(); // 伪代码
                // 3. 使用 ResponseCookie 构建安全 Cookie (推荐方式)
                ResponseCookie authCookie = ResponseCookie.from("authToken", authToken)
                    .httpOnly(true) // 必须!防御 XSS
                    .secure(true) // 必须!仅限 HTTPS (本地开发用 HTTP 时注释掉这行,否则 Cookie 不存储)
                    .path("/") // 应用路径
                    .maxAge(30 * 60) // 30 分钟有效 (根据需要调整)
                    .sameSite("Lax") // 推荐设置防御 CSRF
                    .build();
                // 4. 将 Cookie 添加到响应头
                return ResponseEntity.ok()
                    .header(HttpHeaders.SET_COOKIE, authCookie.toString())
                    .body("Login successful! Welcome, " + username + ".");
            } else {
                return ResponseEntity.status(401).body("Invalid username or password");
            }
        }
        // 模拟登出
        @GetMapping("/logout")
        public ResponseEntity<String> logout(HttpServletResponse response) {
            // 1. 创建一个立即过期、同名、同路径的 Cookie 来覆盖删除
            ResponseCookie deleteCookie = ResponseCookie.from("authToken", "")
                .httpOnly(true)
                .secure(true) // 保持与创建时一致
                .path("/")
                .maxAge(0) // 立即过期
                .sameSite("Lax")
                .build();
            // 2. 添加到响应头
            return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, deleteCookie.toString())
                .body("You have been logged out.");
        }
        private String generateSecureToken() {
            // 实际应用中:应使用安全的随机数生成器,并可能包含用户 ID、过期时间、签名等。
            return "SECURE_RANDOM_TOKEN_" + System.currentTimeMillis(); // 简化示例
        }
    }
    

    测试:

    1. 使用 Postman 或 curl 向 POST http://localhost:8080/login 发送表单数据 (username=admin&password=secret)。检查响应头 Set-Cookie,应包含 authToken 且带有 HttpOnly; Secure; SameSite=Lax 属性(本地开发注释 .secure(true) 后则没有 Secure)。
    2. 访问其他需要认证的端点(假设你有),浏览器会在请求头 Cookie 中自动带上 authToken。
    3. 访问 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() {
            // 1. 创建一个将在子域间共享的 Cookie (例如 Session ID)
            String sharedValue = "SHARED_VALUE_123";
            // 2. 关键:设置 domain 为 '.example.com' (注意前面的点 '.')
            ResponseCookie sharedCookie = ResponseCookie.from("sharedData", sharedValue)
                .httpOnly(true)
                .secure(true) // 生产环境启用
                .path("/") // 根路径
                .maxAge(24 * 60 * 60) // 1 天
                .domain(".example.com") // 允许 example.com 和所有子域访问
                .sameSite("Lax") // 或 None (如果需要跨顶级域)
                .build();
            // 3. 添加到响应头
            return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, sharedCookie.toString())
                .body("Shared cookie has been set for .example.com!");
        }
        @GetMapping("/readsharedcookie")
        public ResponseEntity<String> readSharedCookie(@org.springframework.web.bind.annotation.CookieValue(name = "sharedData", required = false) String sharedValue) {
            if (sharedValue != null && !sharedValue.isEmpty()) {
                return ResponseEntity.ok("Found shared cookie value: " + sharedValue);
            } else {
                return ResponseEntity.ok("No shared cookie found.");
            }
        }
    }
    

    重要说明:

    • 此代码需要在 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)) {
                // 创建 session ID Cookie(实际应使用随机 Token)
                Cookie sessionCookie = new Cookie("sessionID", "generatedSession123");
                sessionCookie.setMaxAge(60 * 60 * 24); // 24 小时过期
                sessionCookie.setPath("/");
                sessionCookie.setHttpOnly(true);
                sessionCookie.setSecure(true); // 假设使用 HTTPS
                response.addCookie(sessionCookie);
                out.println("<html><body>");
                out.println("<h1>Login Successful! Welcome, " + username + ".</h1>");
                out.println("<a href='/yourApp/profile'>Go to Profile</a>");
                out.println("</body></html>");
            } else {
                out.println("<html><body>");
                out.println("<h1>Login Failed. Invalid credentials.</h1>");
                out.println("</body></html>");
            }
        }
    }
    
    @WebServlet("/profile")
    public class ProfileServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            // 检查 session ID Cookie
            Cookie[] cookies = request.getCookies();
            String sessionID = null;
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if ("sessionID".equals(cookie.getName())) {
                        sessionID = cookie.getValue();
                        break;
                    }
                }
            }
            if (sessionID != null && sessionID.equals("generatedSession123")) {
                // 实际应验证 Token
                out.println("<html><body>");
                out.println("<h1>User Profile Page</h1>");
                out.println("<p>You are logged in.</p>");
                out.println("</body></html>");
            } else {
                response.sendRedirect("login.html"); // 重定向到登录页
            }
        }
    }
    
    @WebServlet("/logout")
    public class LogoutServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 删除 session ID Cookie
            Cookie sessionCookie = new Cookie("sessionID", "");
            sessionCookie.setMaxAge(0);
            sessionCookie.setPath("/");
            response.addCookie(sessionCookie);
            response.sendRedirect("login.html");
        }
    }
    
    使用说明
    1. 部署应用,访问 http://localhost:8080/yourApp/login.html。
    2. 登录后,访问 /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
            Cookie[] cookies = request.getCookies();
            String cartValue = "";
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if ("shoppingCart".equals(cookie.getName())) {
                        cartValue = cookie.getValue();
                        break;
                    }
                }
            }
            // 更新购物车(假设商品 ID 用逗号分隔)
            if (!cartValue.isEmpty()) {
                cartValue += "," + productId;
            } else {
                cartValue = productId;
            }
            // 设置新 Cookie
            Cookie cartCookie = new Cookie("shoppingCart", cartValue);
            cartCookie.setMaxAge(60 * 60 * 24 * 7); // 1 周过期
            cartCookie.setPath("/");
            cartCookie.setHttpOnly(true);
            response.addCookie(cartCookie);
            out.println("<html><body>");
            out.println("<h1>Product Added to Cart: " + productId + "</h1>");
            out.println("<a href='/yourApp/viewCart'>View Cart</a>");
            out.println("</body></html>");
        }
    }
    
    @WebServlet("/viewCart")
    public class ViewCartServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            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;
                    }
                }
            }
            out.println("<html><body>");
            out.println("<h1>Your Shopping Cart</h1>");
            if (!cartValue.isEmpty()) {
                List<String> products = Arrays.asList(cartValue.split(","));
                out.println("<ul>");
                for (String product : products) {
                    out.println("<li>Product ID: " + product + "</li>");
                }
                out.println("</ul>");
            } else {
                out.println("<p>Cart is empty.</p>");
            }
            out.println("</body></html>");
        }
    }
    
    @WebServlet("/clearCart")
    public class ClearCartServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 删除购物车 Cookie
            Cookie cartCookie = new Cookie("shoppingCart", "");
            cartCookie.setMaxAge(0);
            cartCookie.setPath("/");
            response.addCookie(cartCookie);
            response.sendRedirect("viewCart");
        }
    }
    
    使用说明
    1. 添加商品链接:<a href="/yourApp/addToCart?id=1">Add Product 1</a>。
    2. 访问 /viewCart 查看购物车;/clearCart 清空。
    3. 注意:实际应用中,商品 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"); // "on" if checked
            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            // 模拟验证
            if ("admin".equals(username) && "password123".equals(password)) {
                // 设置会话 Cookie
                Cookie sessionCookie = new Cookie("sessionID", "generatedSession123");
                sessionCookie.setMaxAge(60 * 60); // 1 小时会话
                sessionCookie.setPath("/");
                sessionCookie.setHttpOnly(true);
                response.addCookie(sessionCookie);
                // 如果选择记住我,设置长期 Cookie(值用简单 Base64 模拟加密)
                if ("on".equals(rememberMe)) {
                    String encodedUsername = Base64.getEncoder().encodeToString(username.getBytes()); // 实际应使用强加密
                    Cookie rememberCookie = new Cookie("rememberUser", encodedUsername);
                    rememberCookie.setMaxAge(60 * 60 * 24 * 30); // 30 天
                    rememberCookie.setPath("/");
                    rememberCookie.setHttpOnly(true);
                    rememberCookie.setSecure(true);
                    response.addCookie(rememberCookie);
                }
                out.println("<html><body>");
                out.println("<h1>Login Successful!</h1>");
                out.println("</body></html>");
            } else {
                out.println("<html><body>");
                out.println("<h1>Login Failed.</h1>");
                out.println("</body></html>");
            }
        }
    }
    
    @WebServlet("/autoLogin")
    public class AutoLoginServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            // 检查记住我 Cookie
            Cookie[] cookies = request.getCookies();
            String encodedUsername = null;
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if ("rememberUser".equals(cookie.getName())) {
                        encodedUsername = cookie.getValue();
                        break;
                    }
                }
            }
            if (encodedUsername != null) {
                // 解密用户名(实际需安全解密)
                String username = new String(Base64.getDecoder().decode(encodedUsername));
                // 自动登录:设置会话 Cookie
                Cookie sessionCookie = new Cookie("sessionID", "generatedSession123");
                sessionCookie.setMaxAge(60 * 60);
                sessionCookie.setPath("/");
                sessionCookie.setHttpOnly(true);
                response.addCookie(sessionCookie);
                out.println("<html><body>");
                out.println("<h1>Auto Login Successful! Welcome back, " + username + ".</h1>");
                out.println("</body></html>");
            } else {
                response.sendRedirect("login.html");
            }
        }
    }
    
    使用说明
    1. 访问 /autoLogin 尝试自动登录。
    2. 注意:实际应用中,应使用强加密(如 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 了。

    目录

    1. 1. Cookie 基础概念
    2. 1.1 什么是 Cookie?
    3. 1.2 Cookie 的作用:为什么需要它?
    4. 1.3 Cookie 的工作原理(通俗解释)
    5. 1.4 Cookie 的属性
    6. 2. Java 中的 Cookie 操作 (Servlet API)
    7. 2.1 创建 Cookie
    8. 2.2 将 Cookie 发送给客户端 (添加到响应)
    9. 2.3 从客户端读取 Cookie (从请求中获取)
    10. 2.4 修改 Cookie
    11. 2.5 删除 Cookie
    12. 2.6 设置 Cookie 的有效期 (setMaxAge)
    13. 2.7 设置 Cookie 的路径 (setPath)
    14. 2.8 设置 Cookie 的域 (setDomain)
    15. 2.9 安全相关属性 (setHttpOnly, setSecure)
    16. 3. Spring Boot 中的 Cookie 操作
    17. 3.1 使用 HttpServletResponse (与传统 Servlet 类似)
    18. 3.2 使用 @CookieValue 注解 (便捷获取)
    19. 3.3 使用 ResponseCookie (Spring Boot 提供的更现代的构建器)
    20. 4. Cookie 的安全性与最佳实践
    21. 4.1 HttpOnly Cookie (防御 XSS)
    22. 4.2 Secure Cookie (防御嗅探)
    23. 4.3 SameSite 属性 (防御 CSRF)
    24. 4.4 签名与加密 (防止篡改)
    25. 4.5 敏感信息不要存储在 Cookie 中
    26. 4.6 Cookie 大小限制
    27. 5. 实战案例:可直接运行的 Spring Boot 示例
    28. 案例 1:基础读写 - 记录用户访问次数
    29. 案例 2:安全设置 - 登录认证 Token (HttpOnly, Secure)
    30. 案例 3:跨域/子域共享 Cookie (Domain, Path)
    31. 案例 4:用户登录状态管理
    32. 完整代码
    33. 使用说明
    34. 案例 5:购物车实现
    35. 完整代码
    36. 使用说明
    37. 案例 6:记住我功能
    38. 完整代码
    39. 使用说明
    40. 6. 高级主题与注意事项
    41. 6.1 Cookie 与 Session 的关系
    42. 6.2 Cookie 的替代方案
    43. 6.3 浏览器限制与隐私政策 (GDPR, CCPA)
    44. 6.4 在 RESTful API 中使用 Cookie
    45. 7. 总结
    • 💰 8折买阿里云服务器限时8折了解详情
    • Magick API 一键接入全球大模型注册送1000万token查看
    • 🤖 一键搭建Deepseek满血版了解详情
    • 一键打造专属AI 智能体了解详情
    极客日志微信公众号二维码

    微信扫一扫,关注极客日志

    微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

    更多推荐文章

    查看全部
    • Windows 环境下 OpenClaw 环境搭建与部署实战
    • Python 基础语法核心知识点梳理
    • Java 动态代理详解:JDK 与 CGLIB 实现对比
    • Python 算术运算符用法及优先级
    • 使用 Docker 安装 Maxwell
    • 鸿蒙 4.2/4.3 安装谷歌框架教程
    • 基于 DeepFace 与 OpenCV 的情绪分析器实现
    • Z-Image-Turbo 图片输出格式优化:PNG 转 JPG/WEBP 方案
    • 用 Prompt 生成正则表达式进行文本匹配实战
    • 图像畸变矫正原理及 MATLAB 与 FPGA 实现
    • 大语言模型 LoRA 研究综述:原理、变体与应用
    • 数据结构入门:顺序表的定义、分类及动态实现
    • Anaconda 安装与 Python 环境配置指南
    • RAG 技术深度解析:架构设计、优化策略与智能客服实践
    • CLI-Anything:让所有软件都能被 AI Agent 原生调用
    • IntelliJ IDEA 2024.3 配置显示 Local Changes 窗口方法
    • 2023 中国大模型落地应用案例集核心洞察
    • Word MCP Server:实现 AI 对 Word 文档的创建、编辑与格式化操作
    • 基于 Vue3+TS+Three.js 的 VR 全景看房应用实战
    • C++ STL 标准库算法详解

    相关免费在线工具

    • Keycode 信息

      查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

    • Escape 与 Native 编解码

      JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

    • JavaScript / HTML 格式化

      使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

    • JavaScript 压缩与混淆

      Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

    • Base64 字符串编码/解码

      将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

    • Base64 文件转换器

      将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online