会话技术

会话技术概述

同一个客户端向服务器中发送的多个请求,需要信息共享。在做服务器开发过程中,我们的客户端和服务器之间,会有请求报文和响应报文:

HTTP 协议的无状态性HTTP协议是无状态的,这意味着服务器默认情况下无法识别两次请求是否来自同一个客户端。

会话技术的实现方式

方式一:客户端携带信息(Cookie)

特点:客户端直接携带确切的信息,这就是客户端技术(Cookie)

方式二:服务器端保险柜(Session

特点:客户端只携带编号,服务器通过编号找到对应的保险柜,这就是服务器技术(Session)

核心区别

归根结底,身份信息的维护方不同:

  • 客户端技术:Cookie – 信息存储在客户端,由客户端维护
  • 服务器技术:Session – 信息存储在服务器,由服务器维护

注意:Session技术是在Cookie技术基础上实现的,Session也需要通过Cookie传递会话ID。

特性CookieSession
信息存储位置客户端(浏览器)服务器
安全性较低(可被查看/篡改)较高(仅在服务器存储)
存储容量约4KB无硬性限制(受服务器内存限制)
生命周期可设置过期时间默认30分钟

客户端技术 Cookie

Cookie 是客户端(浏览器)向服务器发起请求时直接携带的信息,这些信息通过请求头中一个特殊的请求头 Cookie 携带。

Cookie 的格式

Cookie: key1=value1; key2=value2
  • 携带的是键值对信息
  • 值都是字符串类型
  • 多组键值对使用分号 ; 分隔

构造 Cookie 就是让请求头 Cookie 里面包含对应的值。有三种方式:

  1. 浏览器构造 Cookie
  2. ApiFox 构造 Cookie
  3. 服务器构造 Cookie

浏览器构造 Cookie

步骤

  1. 打开开发者工具,快捷键 F12
  2. 找到 应用程序(Application) 标签
  3. 在 存储(Storage) 中找到 Cookie
image-20230217160439567
image-20230217160439567

请求报文示例

GET http://localhost:8083/demo1/cookie/fetch HTTP/1.1
Host: localhost:8083
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: zhangsan=123456; lisi=654321

第17行Cookie: zhangsan=123456; lisi=654321

 ApiFox 构造 Cookie

在 ApiFox 中可以通过 Header 直接设置 Cookie:

<div style=”text-align:center”> <img src=”../../image/04_Conversation/image-20250428201211767.png” alt=”ApiFox设置Cookie” width=”80%”/> </div>

请求报文

GET http://localhost:8083/demo1/cookie/fetch HTTP/1.1
Cookie: zhaoliu=123456; wangwu=789987
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Host: localhost:8083
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

也可以通过 Cookie 管理器 更方便地管理 Cookie:

<div style=”text-align:center”> <img src=”../../image/04_Conversation/image-20250428201453891.png” alt=”Cookie管理器” width=”80%”/> </div>

服务器构造 Cookie(重点)

流程

  1. 客户端 → 服务器:发送请求
  2. 服务器 → 客户端:响应(携带 Set-Cookie 头)
  3. 客户端 → 服务器:后续请求自动携带 Cookie

服务器通过特殊的响应头 Set-Cookie 让浏览器设置 Cookie:

/**
 * 设置 Cookie
 * 访问:http://localhost:8083/demo1/cookie/set?username=lilei
 * 
 * @author stone
 * @date 2023/02/17
 */
@WebServlet("/cookie/set")
public class CookieSetServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        String username = req.getParameter("username");

        // 方式1:通过响应头设置(原始方式)
        resp.setHeader("Set-Cookie", "username=" + username);
        
        // 方式2:使用 Cookie 对象(推荐方式)
        // Cookie cookie = new Cookie("username", username);
        // resp.addCookie(cookie);
    }
}

响应报文

HTTP/1.1 200
Set-Cookie: username=lilei
Content-Length: 0
Date: Fri, 17 Feb 2023 08:15:48 GMT
Keep-Alive: timeout=20
Connection: keep-alive
image-20230217161620669
image-20230217161620669

推荐方式:使用 response.addCookie() 方法:

Cookie cookie = new Cookie("username", username);
resp.addCookie(cookie);

3.3.1 获取方式

@WebServlet("/cookie/fetch")
public class CookieFetchServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 获取所有 Cookie
        Cookie[] cookies = request.getCookies();
        
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                String name = cookie.getName();
                String value = cookie.getValue();
                System.out.println(name + " -> " + value);
            }
        }
    }
}

获取单个 Cookie 的值

/**
 * 获取指定名称的 Cookie 值
 */
private String getCookieValue(HttpServletRequest request, String cookieName) {
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if (cookieName.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
    }
    return null;
}
属性方法说明
name构造方法参数Cookie 的名称(必填)
value构造方法参数Cookie 的值(必填)
PathsetPath(String)Cookie 的有效路径
DomainsetDomain(String)Cookie 的域名,用于跨域共享
MaxAgesetMaxAge(int)Cookie 的过期时间(秒)
HttpOnlysetHttpOnly(boolean)禁止 JavaScript 访问
SecuresetSecure(boolean)仅通过 HTTPS 传输
image-20230217173529919

Path 属性

作用:设置 Cookie 的有效路径,只有访问该路径或其子路径时,浏览器才会携带此 Cookie。

@WebServlet("/cookie/path-demo")
public class PathDemoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 方式1:使用当前请求的默认路径
        Cookie cookie1 = new Cookie("user1", "zhangsan");
        // 默认路径为 /demo1/cookie
        resp.addCookie(cookie1);
        
        // 方式2:手动指定路径
        Cookie cookie2 = new Cookie("user2", "lisi");
        cookie2.setPath("/demo1");  // 整个 /demo1 应用都可访问
        resp.addCookie(cookie2);
        
        // 方式3:根路径
        Cookie cookie3 = new Cookie("user3", "wangwu");
        cookie3.setPath("/");  // 整个域名下都可访问
        resp.addCookie(cookie3);
    }
}

Domain 属性

作用:实现不同子域名间的 Cookie 共享。

@WebServlet("/cookie/domain-demo")
public class DomainDemoServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        Cookie cookie = new Cookie("shared_data", "test_value");
        
        // 设置父域名,子域名可共享此 Cookie
        cookie.setDomain(".example.com");
        cookie.setPath("/");
        
        resp.addCookie(cookie);
    }
}

域名层级示例

image-20230217174121261
image-20230217174121261

注意事项

  • 不能设置与当前域名无关的 domain
  • 访问 localhost 时不能设置 ccc.com 这样的 domain,否则浏览器会忽略

MaxAge 属性

作用:设置 Cookie 的有效期(单位:秒)。

含义
正数Cookie 在指定的秒数后过期,会持久化到硬盘
负数(默认)Cookie 为会话级别,关闭浏览器即失效
0立即删除该 Cookie
@WebServlet("/cookie/maxage-demo")
public class MaxAgeDemoServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 1. 会话级别 Cookie(浏览器关闭即失效)
        Cookie sessionCookie = new Cookie("temp", "value");
        resp.addCookie(sessionCookie);
        
        // 2. 持久化 Cookie(7天有效)
        Cookie persistentCookie = new Cookie("remember", "token123");
        persistentCookie.setMaxAge(7 * 24 * 60 * 60); // 7天
        resp.addCookie(persistentCookie);
        
        // 3. 删除 Cookie
        Cookie cookieToDelete = new Cookie("username", "");
        cookieToDelete.setMaxAge(0);
        cookieToDelete.setPath("/demo1"); // 注意:路径要与原 Cookie 一致
        resp.addCookie(cookieToDelete);
    }
}

安全属性(重点)

HttpOnly

作用:禁止 JavaScript 访问 Cookie,防止 XSS 攻击。

Cookie cookie = new Cookie("session_token", "secret_value");
cookie.setHttpOnly(true);  // JavaScript 无法读取
resp.addCookie(cookie);

攻击场景

// 如果没有 HttpOnly,恶意脚本可以窃取 Cookie
document.cookie; // 获取所有 Cookie

Secure

作用:Cookie 仅通过 HTTPS 协议传输,防止中间人攻击。

Cookie cookie = new Cookie("sensitive_data", "value");
cookie.setSecure(true);  // 仅 HTTPS 传输
resp.addCookie(cookie);

SameSite(现代浏览器支持)

作用:控制 Cookie 在跨站请求中的发送行为,防止 CSRF 攻击。

说明
Strict完全禁止第三方 Cookie,仅同站请求可发送
Lax默认级别,部分跨站请求(如 GET)可发送
None允许所有跨站请求,但必须配合 Secure
// Tomcat 8.5+ 支持 SameSite
// 需要通过 Set-Cookie 头手动设置
resp.setHeader("Set-Cookie", "name=value; SameSite=Stric

关键点说明

  1. URL 编码必要性yyyy-MM-dd HH:mm:ss 格式中的空格需要编码
  2. 持久化存储:设置 MaxAge 使 Cookie 在浏览器关闭后仍然有效
  3. 中文支持:通过 UTF-8 编码支持中文显示

常见问题与注意事项

Cookie 可以存储中文吗?

不能直接存储。Cookie 值不支持特殊字符和中文,需要使用 URL 编码:

// 存储时编码
String encoded = URLEncoder.encode("张三", "UTF-8");
Cookie cookie = new Cookie("name", encoded);

// 读取时解码
String decoded = URLDecoder.decode(cookie.getValue(), "UTF-8");

Cookie 有大小限制吗?

限制项说明
单个 Cookie约 4KB
每个域名 Cookie 数量约 20-50 个(浏览器不同)
总 Cookie 数量约 300-400 个

如何删除 Cookie?

Cookie cookie = new Cookie("name", "");  // 值为空
cookie.setMaxAge(0);                      // 立即过期
cookie.setPath("/demo1");                // 路径必须与原 Cookie 一致
cookie.setDomain(".example.com");        // 域名必须与原 Cookie 一致
resp.addCookie(cookie);

注意:删除 Cookie 时,name、path、domain 必须与原 Cookie 完全一致。

Cookie 的安全风险

风险说明防护措施
XSS 攻击恶意脚本窃取 Cookie设置 HttpOnly
中间人攻击明文传输被截获设置 Secure,使用 HTTPS
CSRF 攻击跨站伪造请求设置 SameSite
信息泄露Cookie 内容被查看不存储敏感信息

Cookie 优缺点

优点缺点
减轻服务器压力存储容量有限(4KB)
可实现跨应用共享数据类型有限(仅字符串)
可设置长期有效安全性较低(客户端可见)
配置灵活每次请求都会携带,增加流量

服务器技术 Session

Session 基本概念

Session 相当于每个用户在服务器上的保险柜

  • 保险柜里可以存储数据(支持敏感数据)
  • 要打开保险柜需要携带钥匙(JSESSIONID)
  • 钥匙丢了就打不开保险柜

Session 的工作原理

第一次请求:创建 Session

当第一次调用 request.getSession() 时:

  1. 服务器创建 Session 对象
  2. 生成唯一的 Session ID(JSESSIONID)
  3. 通过响应头 Set-Cookie 将 JSESSIONID 发送给浏览器
HTTP/1.1 200
Set-Cookie: JSESSIONID=F1500D1D295B3953DCBBF89AD614F1E6; Path=/demo2; HttpOnly
Content-Length: 0
Date: Mon, 20 Feb 2023 06:55:17 GMT

后续请求:携带 Session ID

浏览器自动在 Cookie 中携带 JSESSIONID:

GET http://localhost:8083/demo2/hello HTTP/1.1
Host: localhost:8083
Cookie: JSESSIONID=F1500D1D295B3953DCBBF89AD614F1E6

服务器根据 JSESSIONID 找到对应的 Session 对象。

Session 工作流程

Session 的基本操作

获取 Session

// 方式1:常用方式
// 如果不存在则创建,如果存在则返回已有 Session
HttpSession session = request.getSession();

// 方式2:带参数方式
// true:同方式1
// false:不存在则返回 null,不创建新 Session
HttpSession session = request.getSession(false);

存取数据

Session 是一个键值对存储结构:

  • Key:String 类型
  • Value:Object 类型(可存储任意对象)
@WebServlet("/session/set")
public class SessionSetServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 获取 Session(不存在则创建)
        HttpSession session = req.getSession();
        
        // 存储数据
        session.setAttribute("mobile", "18666778899");
        session.setAttribute("userId", 65536);
        session.setAttribute("user", new User("zhangsan", 20));
    }
}
@WebServlet("/session/get")
public class SessionGetServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        
        HttpSession session = req.getSession();
        
        // 获取数据
        String mobile = (String) session.getAttribute("mobile");
        Integer userId = (Integer) session.getAttribute("userId");
        User user = (User) session.getAttribute("user");
        
        out.println("UserId: " + userId + "<br>");
        out.println("Mobile: " + mobile + "<br>");
        out.println("User: " + user + "<br>");
    }
}

删除数据

// 移除单个属性
session.removeAttribute("mobile");

// 清空所有数据
session.invalidate();  // 同时使 Session 失效

 Session 的生命周期

Session 对象的创建与销毁

事件触发条件
创建第一次调用 request.getSession()
销毁1. 服务器关闭<br>2. 应用卸载<br>3. 调用 session.invalidate()<br>4. 超过有效期(默认30分钟)

数据的生命周期

// 设置 Session 有效期(秒)
session.setMaxInactiveInterval(30 * 60);  // 30分钟

// 获取创建时间
long createTime = session.getCreationTime();

// 获取最后访问时间
long lastAccessTime = session.getLastAccessedTime();

服务器重启与 Session 持久化

Tomcat 默认会将 Session 序列化到硬盘,服务器重启后:

  • Session 对象会被反序列化重新加载
  • JSESSIONID 保持不变
  • 但对象引用会改变(不是同一个对象)

注意:IDEA 内置的 Tomcat 可能无法复现此功能,建议在独立 Tomcat 中测试。

常见问题分析

关闭浏览器,Session 是否被销毁?

:Session 对象没有被销毁,仍然存在于服务器。

原因

  • 浏览器关闭后,内存中的 JSESSIONID Cookie 丢失
  • 再次打开浏览器访问时,没有携带 JSESSIONID
  • 服务器认为是新用户,创建新的 Session

验证方法

// 记录下 JSESSIONID
HttpSession session = req.getSession();
System.out.println("Session ID: " + session.getId());

// 关闭浏览器后,手动在请求中携带之前的 JSESSIONID
// 仍然可以获取到原来的 Session 数据

Session 失效的常见原因

原因说明解决方案
跨域IP 或端口变化,JSESSIONID 无法共享统一域名、使用反向代理
Cookie 被禁用浏览器禁用了 Cookie使用 URL 重写
超时超过 maxInactiveInterval调整超时时间
手动失效调用 invalidate()检查代码逻辑
服务器重启未配置 Session 持久化配置 Session 序列化

禁用 Cookie 后如何使用 Session?

Session 默认依赖 Cookie,如果 Cookie 被禁用,可以使用 URL 重写

// 自动在 URL 后追加 JSESSIONID
String url = resp.encodeURL("/demo2/session/get");
// 结果:/demo2/session/get;JSESSIONID=aaaabc2849ea

resp.sendRedirect(url);

注意:现代应用中,建议要求用户启用 Cookie,URL 重写方式存在安全风险。

Session 并发问题

当同一个用户在不同浏览器/设备同时登录时:

// 方案1:允许同时登录(默认行为) // 每个浏览器获得不同的 Session // 方案2:限制同时登录(踢掉之前的) // 需要配合应用层缓存实现

Session 使用建议

  1. 存储内容:用户 ID、登录状态、购物车等敏感/重要信息
  2. 有效期设置:根据业务场景设置合理的超时时间
  3. 安全性:敏感操作需配合其他验证机制
  4. 内存管理:避免在 Session 中存储大量数据

Cookie 与 Session 对比

核心区别

特性CookieSession
存储位置客户端(浏览器)服务器端
安全性较低(可被查看/篡改)较高(仅在服务器)
存储容量约 4KB无硬性限制
数据类型仅 String任意 Object
生命周期可设置长期有效默认 30 分钟
跨应用可通过 Domain/Path 共享局限于当前应用
服务器压力较高(占用内存)

使用场景选择

适合使用 Cookie 的场景

  1. 记住用户名:7天免登录勾选框
  2. 用户偏好设置:主题、语言等
  3. 非敏感追踪数据:浏览历史(需用户同意)
  4. 跨应用共享数据:单点登录的 Token

适合使用 Session 的场景

  1. 用户登录状态:保存用户 ID、权限信息
  2. 敏感信息:银行卡号、身份证号等
  3. 购物车数据:电商网站的临时购物车
  4. 验证码:防止表单重复提交

会话安全

必设的安全属性

Cookie cookie = new Cookie("token", value);

// 1. HttpOnly:防止 XSS 窃取
cookie.setHttpOnly(true);

// 2. Secure:仅 HTTPS 传输
cookie.setSecure(true);

// 3. SameSite:防止 CSRF
cookie.setPath("/");
// 注意:Java Cookie API 不直接支持 SameSite,需手动设置 Header
resp.setHeader("Set-Cookie", 
    "token=" + value + "; HttpOnly; Secure; SameSite=Strict");

敏感信息处理

// ❌ 错误:直接在 Cookie 中存储明文密码
Cookie badCookie = new Cookie("password", "123456");

// ✅ 正确:只存储随机生成的 Token
Cookie goodCookie = new Cookie("session_token", 
    UUID.randomUUID().toString());
goodCookie.setHttpOnly(true);
goodCookie.setSecure(true);

Session 安全

安全建议

java session.setMaxInactiveInterval(15 * 60); // 15分钟 

  1. 设置合理的超时时间

java // 防止 Session Fixation 攻击 request.changeSessionId(); 

  1. 登录后重新生成 Session ID

java // 修改密码、转账等操作需再次验证 if (!verifyPasswordAgain(req)) { resp.sendError(403); return; } 

  1. 重要操作二次验证

java // 注销时使 Session 失效 session.invalidate(); 

  1. 及时清理 Session

常见问题排查

Session 数据丢失

排查步骤

  1. 检查浏览器是否禁用了 Cookie
  2. 检查域名/端口是否变化(跨域)
  3. 检查 Session 是否超时
  4. 检查是否调用了 invalidate()

Cookie 无法写入

排查步骤

  1. 检查响应是否已提交(Cookie 需在响应前设置)
  2. 检查 Path 是否匹配
  3. 检查 Domain 设置是否正确
  4. 检查浏览器隐私设置

中文乱码

// 存储时编码
Cookie cookie = new Cookie("name", 
    URLEncoder.encode("张三", "UTF-8"));

// 读取时解码
String name = URLDecoder.decode(cookie.getValue(), "UTF-8");

总结

核心知识点

Cookie

  • 客户端技术,存储在浏览器
  • 通过请求头 Cookie 携带,响应头 Set-Cookie 设置
  • 容量限制 4KB,仅支持字符串
  • 重要安全属性:HttpOnly、Secure、SameSite

Session

  • 服务器技术,存储在服务端
  • 通过 JSESSIONID 关联客户端
  • 无容量限制,支持任意对象
  • 默认有效期 30 分钟

选择原则

场景技术选择
非敏感信息、长期存储Cookie
敏感信息、临时存储Session
用户登录状态Session + Cookie(JSESSIONID)
记住登录Cookie(Token)+ Session

常见问题

可以,但通常配合使用效果更好。

  • 只用 Cookie:适合存储非敏感偏好设置
  • 只用 Session:需要配合其他机制传递 Session ID
  • 配合使用:最常用,Session 存敏感数据,Cookie 存标识

如何防止 Session 被劫持

  1. 使用 HTTPS 防止中间人攻击
  2. 设置 HttpOnly 防止 XSS 窃取
  3. 绑定 IP/UserAgent 验证
  4. 设置合理的超时时间
  5. 重要操作二次验证

分布式环境下 Session 如何处理?

  1. Session 复制:Tomcat 集群同步(小集群适用)
  2. Session 共享:使用 Redis/Memcached 集中存储
  3. JWT Token:无状态认证,服务端不存储 Session
  4. Sticky Session:负载均衡绑定特定服务器
  • 隐私模式下 Cookie 不会持久化到硬盘
  • 关闭隐私窗口后所有 Cookie 被清除
  • Session 在服务器端仍然存在,只是客户端丢失了 JSESSIONID
  1. 使用浏览器开发者工具查看 Cookie
  2. 使用 ApiFox/Postman 模拟请求
  3. 在服务端打印 Session ID 和属性
  4. 使用抓包工具(Wireshark、Fiddler)分析请求头

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇