Cookie和Session

Cookie和Session

Servlet全解(一篇就够)-CSDN博客

完整流程

# 首次请求(无Cookie)
GET /login HTTP/1.1
Host: example.com

# 服务器响应(设置Cookie)
HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=28E01391C799D5852AD3F062E9FAC1A2; Path=/; HttpOnly

# 后续请求(浏览器自动添加)
GET /dashboard HTTP/1.1
Host: example.com
Cookie: JSESSIONID=28E01391C799D5852AD3F062E9FAC1A2
  1. 首次请求(无Cookie)

    • 浏览器第一次访问服务器(如 Java Web 应用)
    • 请求头中没有 Cookie 字段
  2. 服务器响应(设置Cookie)

    • 服务器创建会话 → 生成唯一 JSESSIONID(如 28E01391C799D5852AD3F062E9FAC1A2

    • 在响应头中添加:

      Set-Cookie: JSESSIONID=28E01391C799D5852AD3F062E9FAC1A2; Path=/; HttpOnly
      
  3. 浏览器保存

    • 浏览器收到后,将 JSESSIONID 保存到本地(内存/磁盘)
  4. 后续请求(自动携带)

    • 浏览器自动在所有符合条件(同域、Path匹配)的请求头中添加:

      Cookie: JSESSIONID=28E01391C799D5852AD3F062E9FAC1A2
      
角色 职责
服务器 生成 JSESSIONID,通过 Set-Cookie 响应头下发
浏览器 1. 存储收到的 Cookie
2. 自动附加到后续请求头中
3. 管理过期、作用域等
  • 会话型 vs 持久型
    • Expires/Max-Age → 浏览器关闭后删除(会话 Cookie)
    • 有有效期 → 持久保存到过期

对比表格:服务器会话 vs 浏览器Cookie

方面 服务器会话(Session) 浏览器Cookie(JSESSIONID)
存储位置 服务器内存/Redis/数据库 浏览器内存/磁盘
失效触发方 服务器主动控制 浏览器根据规则自动处理
两者关系 会话是本体,Cookie只是钥匙 钥匙,用于查找服务器上的会话

Session(服务器会话)解析

服务器会话Session什么时候创建,什么时候销毁,它有什么功能?

Session 的核心功能

Session 的核心功能是在服务器端维持用户状态,让 HTTP 这种无状态协议能够“记住”用户。主要功能包括:

  1. 用户身份认证 - 存储登录状态
  2. 购物车数据 - 电商网站常用
  3. 表单多页数据暂存 - 跨页面保持数据
  4. 用户偏好设置 - 主题、语言等
  5. 临时数据处理 - 验证码、临时令牌

Session 的创建时机

1.首次请求时自动创建(最常见)

// Java Servlet 中,以下情况会创建 Session:
HttpSession session = request.getSession();      // 如果不存在则创建
HttpSession session = request.getSession(true);  // 明确要求创建

触发条件

  • 第一次访问网站
  • 调用了 request.getSession()request.getSession(true)
    • 请求带Cookie: JSESSIONID=4F58149128D0BCB2 → Tomcat内存查找 → 找到则返回,找不到则新建
  • 如果新建的话,服务器自动创建并生成唯一 Session ID
  • 通过 Set-Cookie 响应头将 JSESSIONID 发给浏览器

Session 的销毁时机

  1. 超时销毁(默认方式)
<!-- web.xml 配置 -->
<session-config>
    <session-timeout>30</session-timeout>  <!-- 30分钟无活动 -->
</session-config>

计时规则

  • 最后一次访问时间开始计算
  • 每次 request.getSession() 都会刷新时间
  • 服务器有定时任务清理过期 Session
  1. 主动销毁(程序控制)
// Java - 退出登录
session.invalidate();  // 立即销毁,抛出异常

// 安全退出建议写法
try {
    HttpSession session = request.getSession(false);
    if (session != null) {
        session.invalidate();
    }
} catch (IllegalStateException e) {
    // 会话已无效
}
  1. 服务器重启
  • 内存中的 Session 数据丢失(除非持久化)
  • 需要配合外部存储(Redis)避免此问题
  1. 服务器强制清理
# Tomcat 配置 context.xml
<Manager className="org.apache.catalina.session.PersistentManager"
         maxActiveSessions="1000"          # 最大会话数
         minIdleSwap="300"                 # 空闲300秒可交换到磁盘
         maxIdleBackup="600"/>             # 空闲600秒备份

Session 生命周期示例

// 完整流程示例
public class SessionLifecycle {
    
    // 1. 用户首次访问(创建)
    public void firstVisit(HttpServletRequest request) {
        HttpSession session = request.getSession();  // 创建新Session
        session.setAttribute("visitCount", 1);
        // 服务器:创建Session对象,生成JSESSIONID
        // 浏览器:收到 Set-Cookie: JSESSIONID=ABC123
    }
    
    // 2. 用户后续操作(保持)
    public void addToCart(HttpServletRequest request, Product product) {
        HttpSession session = request.getSession();  // 获取现有Session
        List<Product> cart = (List<Product>) session.getAttribute("cart");
        cart.add(product);
        // 刷新最后访问时间(30分钟倒计时重置)
    }
    
    // 3. 用户30分钟无操作(超时)
    // 服务器定时任务检测到 lastAccessedTime + 30分钟 < 当前时间
    // → 销毁Session对象,释放内存
    
    // 4. 用户退出登录(主动销毁)
    public void logout(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            session.removeAttribute("user");  // 移除单个属性
            session.invalidate();             // 彻底销毁
            
            // 可选:让浏览器删除Cookie
            Cookie cookie = new Cookie("JSESSIONID", "");
            cookie.setMaxAge(0);
            response.addCookie(cookie);
        }
    }
}

请求的时候,不去getSession()还会创建吗?

只有主动调用 request.getSession()request.getSession(true) 时才会创建 Session。

  1. 默认情况:不创建
// 示例1:不创建Session
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
    // 只是处理请求,没有调用getSession()
    // 服务器不会自动创建Session!
    
    String param = request.getParameter("id");  // OK
    // 到此处,请求处理完毕,没有Session被创建
}
  1. 显式获取(可能创建)
// getSession() - 默认创建
HttpSession session = request.getSession();  
// ↑ 等价于 request.getSession(true)
// 如果Session不存在,则创建新的

// getSession(false) - 不创建
HttpSession session = request.getSession(false);
// ↑ 如果Session不存在,返回null,不创建
  1. 框架行为差异
// Spring Security 可能在认证时自动创建
// 但这不是Servlet容器的默认行为

// 某些框架的拦截器/过滤器可能意外创建Session
// 需要检查框架配置

不必要Session创建的问题:

  1. 内存浪费:每个Session占用几KB到几MB
  2. GC压力:大量短期Session增加垃圾回收频率
  3. 存储开销:集群环境下同步Session的网络开销
  4. 安全风险:更多Session对象增加攻击面

tomcat控制和管理session

Tomcat:实际创建、存储、管理Session对象

创建出来的session都是tomcat那边控制和管理,我的doGet方法里,只是去获取使用

你的代码(doGet方法) -> 获取/使用Session引用 -> Tomcat管理实际Session对象
// 你的Servlet代码(表面)
public class MyServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, 
                         HttpServletResponse response) {
        // 1. 获取Session(问Tomcat要)
        HttpSession session = request.getSession();
        
        // 2. 使用Session(操作Tomcat管理的对象)
        Integer count = (Integer) session.getAttribute("count");
        if (count == null) count = 0;
        session.setAttribute("count", ++count);
        
        // 3. 输出(只是读数据)
        response.getWriter().write("访问次数: " + count);
    }
}

// Tomcat内部(实际发生)
class RequestFacade implements HttpServletRequest {
    public HttpSession getSession() {
        // 委托给Tomcat的Session管理器
        return context.getManager().getSession(this, true);
    }
}

// Tomcat的Session管理器
class StandardManager {
    public Session getSession(Request request, boolean create) {
        String requestedSessionId = request.getRequestedSessionId();
        
        if (requestedSessionId != null) {
            // 1. 查找现有Session
            Session session = sessions.get(requestedSessionId);
            if (session != null && !session.isExpired()) {
                session.access();  // 更新最后访问时间
                return session;
            }
        }
        
        if (create) {
            // 2. 创建新Session
            Session session = createSession();
            
            // 3. 设置Cookie(通过Response)
            Cookie cookie = new Cookie("JSESSIONID", session.getId());
            response.addCookie(cookie);
            
            return session;
        }
        
        return null;
    }
}

Cookie解析

Cookie是服务器发送到用户浏览器并保存在本地的一小块数据。浏览器会存储这些数据,并在后续请求中自动携带它们发送回服务器。

Cookie的生命周期

1. 创建时机
// 服务器端创建Cookie
Cookie cookie = new Cookie("username", "张三");
cookie.setMaxAge(60 * 60 * 24); // 1天
response.addCookie(cookie);     // 通过响应头发送给浏览器

// tomcat + HttpServlet里面
HttpSession session = req.getSession();
// 只要HttpServletRequest执行getSession()方法,回包就会加上Cookie。(tomcat底层代码处理的)

创建场景

  • 用户登录成功时(存储登录令牌)
  • 保存用户偏好设置(语言、主题等)
  • 记录用户行为(购物车、浏览历史)
  • 实现"记住我"功能
2. 销毁时机
// 方式1:设置过期时间为过去
Cookie cookie = new Cookie("username", "");
cookie.setMaxAge(0);  // 立即过期
response.addCookie(cookie);

// 方式2:浏览器行为
// - 到达Expires/Max-Age时间
// - 用户手动清除浏览器数据
// - 隐私模式关闭时

📊 Cookie的类型对比

类型 创建方式 生命周期 存储位置 示例
会话Cookie 不设置Max-Age/Expires 浏览器关闭时 浏览器内存 JSESSIONID
持久Cookie 设置Max-AgeExpires 到达过期时间 浏览器磁盘文件 记住登录状态
安全Cookie setSecure(true) 同上 同上 HTTPS专用
HttpOnly setHttpOnly(true) 同上 同上 防止XSS攻击

Cookie的存储位置

1. 浏览器中的位置
不同浏览器的存储位置:
├── **Chrome**
│   ├── Windows: C:\Users\[用户名]\AppData\Local\Google\Chrome\User Data\Default\Cookies
│   ├── macOS: ~/Library/Application Support/Google/Chrome/Default/Cookies
│   └── Linux: ~/.config/google-chrome/Default/Cookies
├── **Firefox**
│   ├── Windows: C:\Users\[用户名]\AppData\Roaming\Mozilla\Firefox\Profiles\xxx.default\cookies.sqlite
│   └── macOS/Linux: ~/.mozilla/firefox/xxx.default/cookies.sqlite
├── **Edge**: 类似Chrome路径
└── **Safari**
    └── macOS: ~/Library/Cookies/Cookies.binarycookies
2. 存储格式
-- 以SQLite数据库为例(Chrome/Firefox)
CREATE TABLE cookies (
    creation_utc INTEGER,      -- 创建时间
    host_key TEXT,             -- 域名
    name TEXT,                 -- Cookie名称
    value TEXT,                -- Cookie值
    path TEXT,                 -- 路径
    expires_utc INTEGER,       -- 过期时间
    is_secure INTEGER,         -- 是否仅HTTPS
    is_httponly INTEGER,       -- 是否HttpOnly
    last_access_utc INTEGER,   -- 最后访问时间
    has_expires INTEGER,       -- 是否有过期时间
    is_persistent INTEGER      -- 是否持久化
);
  1. 内存 vs 磁盘存储
// 会话Cookie:存储在浏览器内存中
Cookie sessionCookie = new Cookie("session", "abc123");
// 不设置MaxAge → 浏览器内存存储

// 持久Cookie:存储在磁盘文件中  
Cookie persistentCookie = new Cookie("preference", "dark");
persistentCookie.setMaxAge(30 * 24 * 60 * 60); // 30天
// 设置MaxAge → 浏览器磁盘文件存储

总结

浏览器只维护cookie,请求的时候把cookkie再传递给服务器是吗?浏览器会读取cookie里面的信息吗

  1. 浏览器只维护Cookie:存储、管理和清理
  2. 请求时自动传递:符合条件时自动附加到请求头
  3. 浏览器会读取Cookie吗?:会,但是一般不用这个传递信息的。

流程图

服务器发送响应
    ↓
Set-Cookie: name=value; HttpOnly
    ↓
浏览器接收并存储Cookie
    ├── 内存(会话Cookie)
    └── 磁盘文件(持久Cookie)
    ↓
用户发起新请求
    ↓
浏览器自动检查:
    ├── 域名匹配?
    ├── 路径匹配?
    ├── 未过期?
    ├── Secure要求满足?
    └── SameSite策略允许?
    ↓
符合条件的Cookie自动添加到请求头
    ↓
Cookie: name1=value1; name2=value2
    ↓
发送给服务器

常见的数据类型

// 1. 用户身份信息
new Cookie("user_id", "12345");
new Cookie("username", "wdy");
new Cookie("email", "wdy@example.com");

// 2. 会话标识
new Cookie("JSESSIONID", "28E01391C799D5852AD3F062E9FAC1A2");
new Cookie("PHPSESSID", "abc123def456");

// 3. 用户偏好
new Cookie("theme", "dark");
new Cookie("language", "zh-CN");
new Cookie("timezone", "Asia/Shanghai");

// 4. 跟踪/分析数据
new Cookie("_ga", "GA1.2.1234567890.1234567890");
new Cookie("_gid", "GA1.2.9876543210.9876543210");

// 5. 购物车/临时数据
new Cookie("cart_id", "cart_789");
new Cookie("last_viewed", "product_123");

// 6. 认证令牌
new Cookie("auth_token", "eyJhbGciOiJIUzI1NiIs...");
new Cookie("remember_me", "true");

这些发给浏览器,浏览器不会用的吧,它只是保存,然后下次请求的时候,它再传给服务器,服务器从cookie里面拿想要的信息,进行设置,比如拿语言选择,设置语言?

解答:是这样的,浏览器只是保存。

第一次请求(无Cookie)
    ↓
服务器响应(设置Cookie)
    ↓ Set-Cookie: language=zh-CN; theme=dark
浏览器保存Cookie
    ↓ (只存不用)
用户第二次请求
    ↓
浏览器自动附加Cookie
    ↓ Cookie: language=zh-CN; theme=dark
服务器接收并解析Cookie
    ↓
服务器使用Cookie数据
    ├── 看到language=zh-CN → 返回中文页面
    ├── 看到theme=dark → 应用暗黑主题
    ├── 看到user_id=123 → 加载用户数据
    └── 看到JSESSIONID=xxx → 恢复会话

问题:意思就是http是短连接,每次请求就要去校验账号密码的话很麻烦,有了cookie,浏览器请求每次自带cookie?????

完全正确!您已经理解了Cookie最根本的价值!

HTTP是"健忘症患者",Cookie是它的"记忆助手"!

没有Cookie的"地狱模式"

场景:每次请求都要验证身份

# ❌ 地狱版HTTP:每次请求都带账号密码
GET /home HTTP/1.1
Authorization: Basic d2R5OjEyMzQ1Ng==  # base64编码的"wdy:123456"

GET /profile HTTP/1.1  
Authorization: Basic d2R5OjEyMzQ1Ng==

GET /cart HTTP/1.1
Authorization: Basic d2R5OjEyMzQ1Ng==

# 问题:
# 1. 密码在网络中传输N次(安全隐患)
# 2. 服务器每次都要查数据库验证(性能差)
# 3. 用户每次操作都要输密码(体验差)

有Cookie的"天堂模式"

场景:一次登录,处处通行

# ✅ 第一次登录(输入密码)
POST /login HTTP/1.1
Content-Type: application/json

{"username": "wdy", "password": "123456"}

# 服务器响应:给你一个"通行证"
HTTP/1.1 200 OK
Set-Cookie: session_token=abc123def456; HttpOnly; Secure

# ✅ 后续所有请求(自动带通行证)
GET /home HTTP/1.1
Cookie: session_token=abc123def456  # ← 浏览器自动添加!

GET /profile HTTP/1.1  
Cookie: session_token=abc123def456  # ← 自动添加!

GET /cart HTTP/1.1
Cookie: session_token=abc123def456  # ← 自动添加!

Cookie过期 或 Session过期

第一次请求登录后服务器创建session,并且会返回cookie到浏览器,cookie记录登录信息,后面每次浏览器请求就会自动携带cookie,这样服务器就可以根据cookie拿到已经登录的信息。如果cookie过期了,浏览器就得重新登录,如果服务器的session过期了也得浏览器重新登录。

用户第一次登录
    ↓
服务器验证账号密码 ✅
    ↓
创建Session(存服务器) + 设置Cookie(JSESSIONID=xxx)
    ↓
浏览器保存Cookie
    ↓
后续所有请求自动携带Cookie
    ↓
服务器用JSESSIONID查找Session → 获取登录信息
    ↓
会话保持中...
    ↓
❌ Cookie过期 或 ❌ Session过期
    ↓
服务器找不到有效Session
    ↓
返回"需要重新登录"
    ↓
用户重新登录(重新开始循环)

两种过期情况的详细对比

情况1:Cookie过期(钥匙丢了)

// 浏览器端:Cookie过期了
Cookie: JSESSIONID=abc123  // ⏰ 这个Cookie已经过期了!

// 服务器收到的请求:
GET /profile HTTP/1.1
Cookie:  // ⚠️ 空的!或者没有JSESSIONID

// 服务器处理:
HttpSession session = request.getSession(false);  // 返回null
if (session == null) {
    // ❌ 需要重新登录
    response.sendRedirect("/login");
}

情况2:Session过期(保险箱清空了)

// 浏览器端:Cookie还在
Cookie: JSESSIONID=abc123  // ✅ 钥匙还在

// 服务器收到的请求:
GET /profile HTTP/1.1
Cookie: JSESSIONID=abc123  // ✅ 带上了钥匙

// 服务器处理:
HttpSession session = request.getSession(false);
// 但是!Session abc123在服务器上已经过期被清理了!
// session = null

if (session == null) {
    // ❌ 钥匙还在,但保险箱没了
    // 需要重新登录
    response.sendRedirect("/login");
}

过期时间管理策略

1. Cookie过期时间设置

// 场景1:会话Cookie(浏览器关闭就过期)
Cookie sessionCookie = new Cookie("JSESSIONID", sessionId);
// 不设置setMaxAge → 浏览器关闭时删除

// 场景2:持久Cookie(固定时间过期)
Cookie persistentCookie = new Cookie("JSESSIONID", sessionId);
persistentCookie.setMaxAge(7 * 24 * 60 * 60);  // 7天

// 场景3:记住我功能(长时间有效)
Cookie rememberMeCookie = new Cookie("remember_token", token);
rememberMeCookie.setMaxAge(30 * 24 * 60 * 60);  // 30天

2. Session过期时间设置

<!-- Tomcat配置:web.xml -->
<session-config>
    <!-- 默认30分钟无活动就过期 -->
    <session-timeout>30</session-timeout>
</session-config>
// 程序控制
HttpSession session = request.getSession();
session.setMaxInactiveInterval(60 * 60);  // 1小时

HttpSession

工作原理

浏览器首次访问
    ↓
服务器创建 HttpSession 对象
    ↓ 生成唯一 Session ID (如: 28E01391C799D5852AD3F062E9FAC1A2)
    ↓ 存储到服务器内存/Redis
    ↓ 通过 Set-Cookie 响应头发送 JSESSIONID 给浏览器
    ↓
浏览器保存 JSESSIONID Cookie
    ↓
后续请求自动携带 JSESSIONID
    ↓
服务器通过 JSESSIONID 找到对应的 HttpSession
    ↓ 获取/设置 Session 属性
    ↓ 返回响应

tomcat启动后,获取 HttpSession 对象

// 方式1:获取现有Session,如果没有则创建新Session
HttpSession session = request.getSession();      // 相当于 getSession(true)

// 方式2:获取现有Session,如果没有则返回null
HttpSession session = request.getSession(false); // 不自动创建

// 方式3:检查请求中是否包含有效的Session ID
String requestedSessionId = request.getRequestedSessionId();
boolean isValid = request.isRequestedSessionIdValid();
// 设置属性(存储数据)
HttpSession session = req.getSession();
session.setAttribute("user", "张三");
session.setAttribute("count", 100);

Session 存储机制

1. 默认存储(内存)

// Tomcat 内部实现(简化版)
public class StandardManager implements Manager {
    
    // 使用ConcurrentHashMap存储所有Session
    private Map<String, Session> sessions = new ConcurrentHashMap<>();
    
    // 创建新Session
    public Session createSession() {
        String sessionId = generateSessionId();
        StandardSession session = new StandardSession(this);
        session.setId(sessionId);
        session.setCreationTime(System.currentTimeMillis());
        sessions.put(sessionId, session);
        return session;
    }
    
    // 后台线程清理过期Session
    public void backgroundProcess() {
        for (Session session : sessions.values()) {
            if (session.isExpired()) {
                session.expire();
                sessions.remove(session.getId());
            }
        }
    }
}

2. 分布式 Session 存储

<!-- 使用 Redis 存储 Session -->
<Context>
    <Manager className="org.apache.catalina.session.PersistentManager"
             saveOnRestart="true"
             maxActiveSessions="10000">
        <Store className="org.apache.catalina.session.RedisStore"
               host="redis-cluster.example.com"
               port="6379"
               password="${redis.password}"
               database="0"
               timeout="2000"
               sentinelMaster="mymaster"
               sentinels="sentinel1:26379,sentinel2:26379"/>
    </Manager>
</Context>

保存的一个实例

@WebServlet("/test")
public class EncodingTest extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        HttpSession session = req.getSession();
        System.out.println("session id: " + session.getId());
        System.out.println("session : " + session);
        System.out.println("session : " + session.getAttribute("user") + " " + session.getAttribute("count"));

        session.setAttribute("user", "张三");
        session.setAttribute("count", 100);
    }
}

image-20260202100036417

问题:社区版IDEA,因为没有tomcat的选项,只能安装Smart Tomcat插件。然后用这个启动,请求一次/test,会创建session和设置属性。

重启tomcat,在执行请求一次/test,发现直接能拿到上次设置的属性?这是为什么?哪里持久化了HttpSession吗?

解答:

Smart Tomcat自动持久化了Session → 重启后恢复。持久化文件存放的地方不知道,搜了都搜不到。

posted @ 2026-02-14 14:29  deyang  阅读(3)  评论(0)    收藏  举报