技术演进中的开发沉思-140 java-servlet篇:Cookie
记得第一次用 Cookie 解决 “登录状态保持” 问题 —— 那时才明白,Cookie 就像用户访问网站时的 “临时身份证”:服务器给用户发一张写有身份信息的小纸条(Cookie),用户下次访问时带着纸条,服务器就能快速认出 “你是谁”,不用每次都重新登录。

在 Servlet 中,Cookie 的核心作用是 “在客户端存储少量状态数据”,弥补 HTTP 协议 “无状态” 的缺陷。就像医院给病人发就诊卡,卡上记录基本信息,病人复诊时不用再填一遍表单。它的工作流程分三步:
- 服务器发放:Servlet 通过response.addCookie()生成 Cookie,随响应发送到客户端;
- 客户端存储:浏览器将 Cookie 保存在本地(内存或硬盘);
- 客户端携带:下次访问同一网站时,浏览器自动将 Cookie 随request.getCookies()发送给服务器。
早年做电商网站时,我曾因没理解 Cookie 的 “域名绑定” 特性,导致用用户登录后,访问二级域名时时又要重新登录 —— 就像病人拿着 A 医院的就诊卡去 B 医院,自然无法识别。理解 Cookie 的设计逻辑,是做好 Web 身份验证的基础。
一、从创建到使用的 Cookie 实战
1. 会话 Cookie 与持久 Cookie
会话 Cookie 像 “临时身份证”,关闭浏览器就失效;持久 Cookie 像 “长期居住证”,能在客户端保存指定时间:
// 电商登录Servlet:创建会话Cookie与持久Cookie
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
boolean rememberMe = "on".equals(req.getParameter("rememberMe"));
// 1. 验证用户(实际项目需加密校验)
User user = UserService.verify(username, password);
if (user == null) {
resp.getWriter().write("账号或密码错误");
return;
}
// 2. 创建会话Cookie:保存用户ID,关闭浏览器失效(默认)
Cookie userIdCookie = new Cookie("userId", user.getId().toString());
userIdCookie.setPath("/"); // 全站可用(所有路径都能访问该Cookie)
// 3. 创建持久Cookie:若勾选“记住我”,保存7天
Cookie rememberCookie = new Cookie("rememberMe", username);
rememberCookie.setPath("/");
if (rememberMe) {
rememberCookie.setMaxAge(7 * 24 * 60 * 60); // 有效期7天(秒为单位)
} else {
rememberCookie.setMaxAge(0); // 不记住:立即删除Cookie
}
// 4. 发送Cookie到客户端
resp.addCookie(userIdCookie);
resp.addCookie(rememberCookie);
resp.getWriter().write("登录成功,跳转首页...");
}
}
// 首页Servlet:读取Cookie识别用户
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Cookie[] cookies = req.getCookies();
String userId = null;
String username = null;
// 遍历Cookie查找目标数据(注意:cookies可能为null)
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("userId".equals(cookie.getName())) {
userId = cookie.getValue();
} else if ("rememberMe".equals(cookie.getName())) {
username = cookie.getValue();
}
}
}
// 根据Cookie判断登录状态
if (userId != null) {
resp.getWriter().write("欢迎回来," + username + "!用户ID:" + userId);
} else {
resp.getWriter().write("请先登录");
resp.sendRedirect("/login"); // 未登录跳转登录页
}
}
}
2. 安全属性配置(支付模块防劫持)
给 Cookie 添加安全属性,就像给 “身份证” 加安全锁,防止被篡改或窃取:
// 支付Servlet:配置安全Cookie
public class PayServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1. 生成支付会话标识(防止CSRF攻击)
String payToken = UUID.randomUUID().toString();
// 2. 创建安全Cookie
Cookie payTokenCookie = new Cookie("payToken", payToken);
payTokenCookie.setPath("/pay"); // 仅支付相关路径可用,缩小作用域
payTokenCookie.setMaxAge(30 * 60); // 有效期30分钟
// 安全属性配置
payTokenCookie.setHttpOnly(true); // 禁止JS访问,防XSS攻击(关键!)
payTokenCookie.setSecure(true); // 仅HTTPS传输,防中间人窃取
payTokenCookie.setSameSite("Strict"); // 严格限制跨站携带,防CSRF攻击
resp.addCookie(payTokenCookie);
resp.getWriter().write("支付会话已创建,token:" + payToken);
}
}
三、Cookie 踩坑记
曾开发web系统时,遇到一个典型问题:用户在办公室电脑勾选 “记住我” 登录后,回家用家里电脑访问,却无法保持登录状态。排查后发现,Cookie 的setPath("/oa")设置成了 OA 系统的子路径,而家里电脑访问的是根域,导致 Cookie 无法被识别 —— 就像把 “办公楼门禁卡” 做成了 “某楼层门禁卡”,出了楼层就失效。
解决办法是将setPath("/"),确保整个域名下的所有路径都能访问该 Cookie。但新问题又出现了:财务模块的敏感 Cookie(如审批权限标识),在非财务路径也能被读取。最终通过 “路径隔离” 优化:普通 Cookie 设path="/",敏感 Cookie 设path="/oa/finance",既保证登录状态全站可用,又防止敏感数据泄露。
另一次是 一次风控事件, Cookie 劫持事件:黑客通过 XSS 攻击窃取用户的支付 Cookie,伪造支付请求。后来我们给所有支付相关 Cookie 添加HttpOnly=true和Secure=true,就像给 “银行卡密码” 加了两层锁 ——JS 无法读取,只能通过 HTTPS 传输,彻底杜绝了劫持风险。
四、不同类型 Cookie 的 “优劣对决”
|
Cookie 类型 |
核心特性 |
像什么场景 |
电商 / OA 适配性 |
痛点 |
|
会话 Cookie |
内存存储,关闭浏览器失效 |
临时身份证,当天有效 |
普通登录、临时会话 |
关闭浏览器需重新登录 |
|
持久 Cookie |
硬盘存储,有过期时间 |
长期居住证,有效期内可用 |
“记住我” 功能、自动登录 |
长期存储有安全风险 |
|
安全 Cookie(HttpOnly+Secure) |
禁止 JS 访问,仅 HTTPS 传输 |
带密码锁的身份证,仅限安全通道使用 |
支付、财务审批等敏感模块 |
非 HTTPS 环境无法使用 |
|
跨域 Cookie |
跨不同域名共享 |
跨国护照,多地区通用 |
多系统单点登录(SSO) |
浏览器限制多,配置复杂 |
2020 年我们做过一次用户体验测试:会话 Cookie 的登录成功率约 85%(用户常关闭浏览器),持久 Cookie(7 天有效期)的登录成功率提升到 98%,但安全风险增加了 30%。这也说明:Cookie 的选择需在 “用户体验” 和 “安全” 之间找平衡 —— 普通功能用持久 Cookie,敏感功能用会话 Cookie + 安全属性。
五、踩过的 “Cookie 坑”
路径与域名陷阱
有个同事在 Servlet 中创建 Cookie 时,没设置setPath(),默认路径是当前请求路径(如/login),导致在/index路径无法读取 Cookie。这就像把 “图书馆借阅卡” 的使用范围设为 “借阅处”,到 “阅览室” 就无法使用。
解法:根据需求明确设置setPath(),全站可用设/,局部功能设具体路径(如/pay)。
安全属性遗漏
某 OA 系统的会话 Cookie 没加HttpOnly,被 XSS 攻击窃取用户身份,导致未授权用户访问审批页面。这就像把 “家门钥匙” 放在门口,任何人都能拿走。
解法:所有涉及身份、权限的 Cookie,必须添加HttpOnly=true;涉及支付、财务的 Cookie,额外添加Secure=true和SameSite=Strict。
跨域 Cookie 失效
电商平台想在两个系统之间共享登录 Cookie,直接创建 Cookie 后发现无法跨域读取。这就像用 “北京身份证” 在上海办理业务,系统不认可。
原理:浏览器默认禁止跨域携带 Cookie,需配合 CORS(跨域资源共享)配置。
解法:服务器端设置响应头Access-Control-Allow-Credentials=true,客户端 AJAX 请求设withCredentials=true,同时 Cookie 需设置domain=".company.com"(主域名一致)。
大小超限陷阱
有个同事在 Cookie 中存储大量用户信息(如完整个人资料),导致 Cookie 大小超过 4KB,被浏览器自动截断。这就像把 “10 页的简历” 塞进 “1 页的身份证”,多余内容会丢失。
解法:Cookie 仅存储少量关键数据(如用户 ID、令牌),大量数据存数据库或 Redis,通过 Cookie 中的关键数据查询。
六、最佳实践:安全高效的 Cookie 使用指南
设计层面
- 会话 Cookie 与持久 Cookie 分工:普通登录用持久 Cookie(7~30 天),敏感操作(支付、改密码)用会话 Cookie;
- 数据最小化:Cookie 仅存 ID、令牌等轻量数据(不超过 1KB),避免存储 JSON、HTML 等大内容;
- 路径与域名精准配置:根据功能范围设置setPath()和setDomain(),缩小 Cookie 作用域,降低泄露风险。
安全层面
- 敏感 Cookie 必加三大属性:HttpOnly=true(防 XSS)、Secure=true(防中间人)、SameSite=Strict/Lax(防 CSRF);
- 定期更新 Cookie:如支付令牌每 30 分钟刷新一次,避免长期有效导致风险;
- 加密存储:Cookie 中的敏感数据(如用户 ID)需加密(如 AES 加密),防止明文泄露。
性能层面
- 减少 Cookie 数量:同一功能尽量合并 Cookie(如用一个 Cookie 存储多个键值对,用分隔符分隔),避免浏览器每次请求携带大量 Cookie;
- 合理设置过期时间:短期会话设MaxAge=-1(会话结束失效),长期功能设MaxAge=30*24*60*60(30 天),避免 Cookie 长期占用客户端空间;
- 静态资源免 Cookie:将图片、JS、CSS 等静态资源部署到无 Cookie 域名,减少请求携带的 Cookie 体积,提升加载速度。
七、云原生时代的 Cookie 演进
如今都是都用 JWT 令牌了,还需要 Cookie 吗?” 我笑着打开他们的登录模块 ——JWT 令牌依然是通过 Cookie 存储在客户端,只是 Cookie 的 “角色” 从 “存储数据” 变成了 “携带令牌的容器”。
云原生时代的 Cookie,有了新的应用场景:
- 微服务单点登录(SSO):在auth.company.com生成登录 Cookie,设置domain=".company.com",让shop.company.com、pay.company.com等微服务共享登录状态;
- 分布式追踪:在 Cookie 中存储 Trace ID,跨微服务追踪请求链路,就像给 “快递包裹” 贴跟踪码,全程监控流转过程;
- A/B 测试:通过 Cookie 存储用户的测试分组(如 A 组用新版界面,B 组用旧版),确保用户在会话中体验一致的版本。
最后小结
之前处理Cookie 处理代码,那时还没有HttpOnly属性,只能手动加密数据防泄露;现在的代码里,HttpOnly、Secure、SameSite已成标配 —— 不是 Cookie 变复杂了,而是 Web 安全的要求更高了。
Cookie 就像 Web 世界的 “老伙计”,从早期的简单身份标识,到现在的安全凭证载体,始终在 “无状态” 的 HTTP 协议中,默默承担着 “保持状态” 的重任。它虽小(仅 4KB),却支撑着电商登录、OA 审批、支付验证等核心功能 —— 就像老木匠手中的 “小锤子”,虽不起眼,却能打造出复杂的家具。理解 Cookie 的设计逻辑、安全属性和使用技巧,不仅是写好 Servlet 的关键,更是理解 Web 技术 “用户体验与安全平衡” 的窗口。毕竟,再先进的身份验证方案,最终都需要一个 “载体” 传递给客户端 —— 而 Cookie,依然是最成熟、最通用的选择之一。
更多推荐
所有评论(0)