深度解析Java视角下Cookie、Session、Token实战教程
在Java Web开发中,用户身份认证与状态保持是核心需求之一。Cookie、Session、Token作为三种主流的状态管理机制,贯穿于从传统JSP/Servlet项目到现代Spring Boot/Cloud微服务架构的各类应用中。很多开发者对三者的区别、适用场景及底层实现一知半解,本文将从Java技术栈出发,结合源码级解析与实战案例,全面拆解Cookie、Session、Token的核心逻辑,帮助大家构建清晰的知识体系。
一、前置知识:HTTP协议的无状态性
要理解Cookie、Session、Token的设计初衷,首先要明确HTTP协议的核心特性——无状态性。HTTP协议本身不保存客户端与服务器之间的交互状态,即服务器无法通过HTTP协议自动识别两次请求是否来自同一客户端。例如:用户第一次访问服务器登录成功后,第二次请求时服务器无法直接知晓该用户已登录,这就导致无法实现购物车、个人中心等需要状态保持的功能。
为了解决HTTP无状态性带来的问题,Cookie、Session、Token应运而生。三者的核心目标都是实现“客户端身份识别”与“状态保持”,但实现思路、存储位置、安全特性存在本质差异。
二、Cookie:客户端的状态载体
2.1 什么是Cookie?
Cookie是服务器发送给客户端浏览器的一小段文本数据(通常不超过4KB),浏览器会将其保存到本地(内存或磁盘)。之后,客户端每次向同一服务器发送请求时,都会自动携带该Cookie数据,从而让服务器识别出客户端身份。
从Java Web角度看,Cookie是Servlet规范中的标准组件,由javax.servlet.http.Cookie类封装,服务器通过response对象向客户端写入Cookie,通过request对象读取客户端携带的Cookie。
2.2 Cookie的核心原理与Java实现
2.2.1 核心原理
- 客户端首次请求服务器时,服务器通过HTTP响应头的
Set-Cookie字段将Cookie数据发送给客户端; - 客户端浏览器接收后,根据Cookie的属性(如过期时间、路径、域名)保存到本地;
- 客户端后续请求同一服务器时,会在HTTP请求头的
Cookie字段中携带本地保存的Cookie数据; - 服务器解析请求头中的Cookie数据,识别客户端身份并获取状态信息。
2.2.2 Java实战:Cookie的创建与使用
以下是基于Servlet的Cookie核心操作示例,涵盖Cookie的创建、写入客户端、读取及销毁:
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;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/cookieDemo")
public class CookieDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 1. 读取客户端携带的Cookie
Cookie[] cookies = request.getCookies();
if (cookies != null) {
out.write("客户端携带的Cookie:
");
for (Cookie cookie : cookies) {
// 获取Cookie的名称和值
String name = cookie.getName();
String value = cookie.getValue();
out.write("名称:" + name + ",值:" + value + "
");
}
} else {
out.write("客户端首次访问,未携带Cookie
");
}
// 2. 创建Cookie并写入客户端
Cookie userCookie = new Cookie("username", "zhangsan");
// 设置Cookie的过期时间:单位为秒,正数表示持久化到磁盘,负数表示仅存于内存(会话结束后销毁),0表示立即删除
userCookie.setMaxAge(3600);
// 设置Cookie的有效路径:仅当请求路径匹配该路径时,才携带此Cookie
userCookie.setPath("/");
// 设置Cookie的有效域名:指定哪些域名可以访问该Cookie,防止跨域盗用
userCookie.setDomain("localhost");
// 设置Cookie为HttpOnly:禁止JavaScript读取,防止XSS攻击
userCookie.setHttpOnly(true);
// 设置Cookie为Secure:仅在HTTPS协议下才携带此Cookie
// userCookie.setSecure(true);
// 将Cookie写入响应
response.addCookie(userCookie);
// 3. 销毁Cookie(通过设置maxAge为0实现)
Cookie deleteCookie = new Cookie("oldCookie", "");
deleteCookie.setMaxAge(0);
deleteCookie.setPath("/");
response.addCookie(deleteCookie);
out.close();
}
}
2.3 Cookie的核心属性详解
属性名 | 作用 | Java方法 | 注意事项 |
|---|---|---|---|
Name | Cookie的名称,唯一标识一个Cookie | setName(String name) | 名称一旦确定,无法修改,只能通过同名Cookie覆盖 |
Value | Cookie存储的文本数据 | setValue(String value) | 不能包含空格、逗号、分号等特殊字符,需URL编码 |
Max-Age | 过期时间(秒) | setMaxAge(int maxAge) | 默认-1(内存存储),0表示立即删除,正数表示持久化 |
Path | 有效路径,仅匹配路径的请求才携带Cookie | setPath(String path) | 默认是当前Servlet的路径,设置为“/”表示整个应用有效 |
Domain | 有效域名,限制Cookie的访问范围 | setDomain(String domain) | 例如“example.com”表示子域名(www.example.com)也可访问 |
HttpOnly | 禁止JavaScript读取Cookie | setHttpOnly(boolean httpOnly) | 有效防止XSS攻击,Java EE 6及以上支持 |
Secure | 仅在HTTPS协议下携带Cookie | setSecure(boolean secure) | 提升安全性,生产环境建议开启 |
2.4 Cookie的优缺点
优点
- 实现简单:基于HTTP标准,Java Servlet原生支持,开发成本低;
- 减轻服务器压力:数据存储在客户端,无需服务器额外存储;
- 跨请求共享:自动携带,无需客户端手动处理。
缺点
- 容量限制:单个Cookie不超过4KB,多个Cookie总数有限(不同浏览器限制不同,通常20-50个);
- 安全性差:默认可被JavaScript读取(未设置HttpOnly时),易遭受XSS攻击;可能被篡改,需加密处理;
- 数据类型限制:仅支持文本数据,无法存储复杂对象;
- 跨域限制:受同源策略限制,无法在不同域名间共享(除非特殊配置)。
三、Session:服务器端的状态管理
3.1 什么是Session?
Session(会话)是服务器为每个客户端(浏览器)创建的内存级状态对象,用于存储客户端的会话信息(如登录状态、购物车数据等)。服务器通过Session ID唯一标识每个Session,而Session ID通常通过Cookie发送给客户端,客户端后续请求时携带Session ID,服务器通过该ID找到对应的Session对象,从而实现状态保持。
在Java Web中,Session由Servlet容器(如Tomcat、Jetty)管理,通过javax.servlet.http.HttpSession接口封装,开发者无需手动管理Session的创建、销毁及Session ID的传递。
3.2 Session的核心原理与Java实现
3.2.1 核心原理
- 客户端首次请求服务器时,服务器创建一个Session对象,生成唯一的Session ID;
- 服务器将Session ID通过Cookie(默认名为JSESSIONID)发送给客户端;
- 客户端浏览器保存该Cookie(默认Max-Age为-1,仅存于内存);
- 客户端后续请求时,携带JSESSIONID Cookie,服务器通过Session ID查找对应的Session对象,获取客户端状态。
注意:Session的底层依赖Cookie传递Session ID,但也支持URL重写(将Session ID拼接在URL后)作为Cookie禁用时的替代方案。
3.2.2 Java实战:Session的创建与使用
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
@WebServlet("/sessionDemo")
public class SessionDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 1. 获取Session(若不存在则创建新Session)
HttpSession session = request.getSession();
// 获取Session ID
String sessionId = session.getId();
out.write("Session ID:" + sessionId + "
");
// 获取Session创建时间
Date createTime = new Date(session.getCreationTime());
out.write("Session创建时间:" + createTime + "
");
// 获取最后访问时间
Date lastAccessTime = new Date(session.getLastAccessedTime());
out.write("最后访问时间:" + lastAccessTime + "
");
// 2. 向Session中存储数据(支持任意Java对象)
session.setAttribute("user", new User("zhangsan", 20));
session.setAttribute("isLogin", true);
// 3. 从Session中读取数据
User user = (User) session.getAttribute("user");
boolean isLogin = (boolean) session.getAttribute("isLogin");
out.write("当前登录用户:" + user.getName() + ",年龄:" + user.getAge() + "
");
out.write("登录状态:" + (isLogin ? "已登录" : "未登录") + "
");
// 4. 设置Session的过期时间(单位:秒)
// 方式1:通过API设置(优先级高于web.xml配置)
session.setMaxInactiveInterval(1800); // 30分钟无操作过期
// 方式2:在web.xml中配置(全局生效)
/*
<session-config>
<session-timeout>30</session-timeout> <!-- 单位:分钟 -->
</session-config>
*/
// 5. 手动销毁Session(用于退出登录)
// session.invalidate();
// 6. URL重写(Cookie禁用时使用)
String url1 = response.encodeURL("/test");
String url2 = response.encodeRedirectURL("/test");
out.write("重写后的URL:" + url1 + "
");
out.close();
}
// 自定义User类(需实现Serializable接口,支持Session钝化/活化)
static class User implements java.io.Serializable {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// getter/setter
public String getName() { return name; }
public int getAge() { return age; }
}
}
3.3 Session的核心机制:钝化与活化
由于Session默认存储在服务器内存中,当服务器重启或Session数量过多时,会导致内存溢出或Session丢失。因此,Servlet容器提供了Session钝化(Passivation)与活化(Activation)机制:
- 钝化:当Session长时间未被访问或服务器内存紧张时,容器将Session对象序列化到磁盘文件(如Tomcat的work目录);
- 活化:当客户端再次请求该Session时,容器将磁盘中的Session文件反序列化为内存对象,恢复会话状态。
注意:要实现Session钝化/活化,Session中存储的对象必须实现java.io.Serializable接口,否则会抛出序列化异常。
3.4 Session的优缺点
优点
- 安全性高:数据存储在服务器端,客户端无法直接修改;
- 数据类型灵活:支持存储任意Java对象,无需手动序列化;
- 容量无限制:仅受服务器内存限制,可存储大量会话数据。
缺点
- 服务器压力大:每个会话对应一个内存对象,高并发场景下会占用大量服务器资源;
- 分布式部署问题:Session存储在单个服务器节点,分布式架构下需要实现Session共享(如Redis集群、Tomcat集群Session复制);
- 依赖Cookie:默认通过Cookie传递Session ID,若客户端禁用Cookie,需使用URL重写(安全性低且不美观);
- 状态丢失风险:服务器重启或崩溃时,未钝化的Session会丢失。
四、Token:无状态的身份凭证
4.1 什么是Token?
Token(令牌)是服务器为客户端生成的加密字符串凭证,包含客户端身份信息、过期时间等核心数据。客户端登录成功后,服务器生成Token并返回给客户端,客户端将Token存储在本地(Cookie、LocalStorage、SessionStorage等),后续请求时通过请求头(如Authorization)携带Token,服务器通过解密Token验证客户端身份,无需在服务器端存储会话状态。
Token是解决分布式架构下Session共享问题的核心方案,主流的Token标准有JWT(JSON Web Token)、OAuth2.0等。在Java开发中,常用JJWT(Java JWT)库实现JWT的生成与验证。
4.2 Token的核心原理(以JWT为例)
JWT由三部分组成,用“.”分隔:Header.Payload.Signature,整体结构如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InpoYW5nc2FuIiwiaWF0IjoxNzEzMzM4NzY0LCJleHAiOjE3MTMzNDIzNjR9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
4.2.1 各部分解析
- Header(头部):指定Token的类型(JWT)和加密算法(如HS256、RS256),示例:
{ "alg": "HS256", // 哈希算法:HMAC SHA256"typ": "JWT" // 令牌类型 }头部会经过Base64URL编码后作为JWT的第一部分。 - Payload(载荷):存储核心数据(如用户ID、用户名、过期时间),分为标准声明、公共声明和私有声明: 示例:
{"username": "zhangsan", // 私有声明"iat": 1713338764, // 签发时间(时间戳)"exp": 1713342364 // 过期时间(时间戳,此处为1小时后)}载荷也会经过Base64URL编码后作为JWT的第二部分(注意:Base64URL编码是可逆的,因此不要在载荷中存储敏感信息,如密码)。 - 标准声明:JWT规定的默认字段(可选),如iss(签发者)、exp(过期时间)、iat(签发时间)、sub(主题)等;
- 公共声明:自定义的公共字段,可被任意客户端读取;
- 私有声明:客户端与服务器约定的私有字段,用于存储业务相关信息。
- Signature(签名):对Header和Payload的编码结果进行加密,确保Token不被篡改。加密过程如下:
Signature = HMACSHA256(base64UrlEncode(Header) + "." + base64UrlEncode(Payload),密钥(secret))服务器通过密钥验证签名的有效性:若Token被篡改,编码后的Header和Payload会发生变化,签名验证将失败。
4.2.2 JWT的核心流程
- 客户端提交用户名/密码到服务器登录接口;
- 服务器验证 credentials 有效后,生成JWT Token(包含用户信息和过期时间);
- 服务器将Token返回给客户端(如JSON响应);
- 客户端存储Token(如LocalStorage),后续请求时在Authorization头中携带:
Authorization: Bearer <Token>; - 服务器接收请求后,解析Token,验证签名和过期时间;
- 验证通过则处理请求,验证失败则返回401 Unauthorized。
4.3 Java实战:基于JJWT实现JWT
4.3.1 引入依赖(Maven)
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>4.3.2 实现JWT工具类
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "qcby";
/**
* 创建token
* @param id //用户id
* @param subject // 用户名
* @param ttlMillis // 有效期
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
//签名时间
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(id) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("wd") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);// 设置过期时间
return builder.compact();
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
public static void main(String[] args) {
String token = JwtUtil.createJWT(UUID.randomUUID().toString(),"qd",null );
System.out.println(token);
}
}4.4 Token的优缺点
优点
- 无状态:服务器无需存储会话状态,减轻服务器压力,易于分布式部署;
- 跨域支持:Token通过请求头携带,不受同源策略限制,可用于跨域认证(如前后端分离、微服务架构);
- 灵活性高:可存储自定义数据,支持多种客户端(浏览器、APP、小程序等);
- 安全性可控:通过加密签名确保不被篡改,支持非对称加密(如RS256)提升安全性。
缺点
- 无法主动销毁:Token一旦生成,在过期前始终有效,若需注销登录,需在客户端删除Token或服务器维护黑名单(增加复杂度);
- 载荷不可靠:Base64URL编码可逆,不能存储敏感信息;
- 性能开销:每次请求都需解密验证签名,高并发场景下会有一定性能损耗(可通过缓存优化);
- 实现复杂度高:需手动处理Token的生成、验证、解析,以及过期刷新机制(Session由容器自动管理)。
五、Cookie、Session、Token核心对比与适用场景
5.1 核心维度对比
对比维度 | Cookie | Session | Token(JWT) |
|---|---|---|---|
存储位置 | 客户端(浏览器) | 服务器端(内存/磁盘) | 客户端(任意存储方式) |
数据大小 | 单个≤4KB,总数有限 | 无限制(受服务器内存) | 无严格限制(建议不超过1KB,避免请求头过大) |
数据类型 | 仅文本 | 任意Java对象 | 文本(JSON格式) |
状态管理 | 客户端状态 | 服务器端状态 | 无状态 |
安全性 | 较低(易被篡改、XSS攻击) | 较高(数据在服务器端) | 中高(签名验证,可防篡改) |
分布式支持 | 天然支持(客户端携带) | 需额外实现共享(Redis/集群复制) | 天然支持(无状态) |
实现复杂度 | 低(Servlet原生支持) | 低(容器自动管理) | 高(需手动处理生成、验证、刷新) |
5.2 适用场景推荐
- Cookie适用场景:
- 存储少量非敏感数据,如用户偏好设置、主题配置;
- 配合Session传递Session ID;
- 简单的身份标识(如记住登录状态,需加密)。
- Session适用场景:
- 单体应用(非分布式)的用户认证与状态保持;
- 需要存储大量会话数据的场景(如购物车);
- 对安全性要求较高,且无需跨域的场景。
- Token适用场景:
- 分布式架构、微服务架构(无需Session共享);
- 前后端分离项目(前端独立部署,跨域请求);
- 多客户端认证(浏览器、APP、小程序等统一认证);
- 第三方接口授权(如OAuth2.0协议)。
六、总结与最佳实践
Cookie、Session、Token并非对立关系,而是互补关系,核心目标都是解决HTTP无状态性带来的状态保持问题。在实际开发中,需根据项目架构、安全性要求、客户端类型等因素选择合适的方案:
- 单体应用优先选择Session:实现简单,安全性高,容器自动管理,无需手动处理复杂逻辑;
- 分布式/前后端分离应用优先选择Token(JWT):无状态特性适配分布式部署,跨域支持好,适合多客户端;
- Cookie始终作为辅助工具:可用于存储少量非敏感数据,或配合Session/Token实现状态传递(如设置HttpOnly Cookie存储Token,提升安全性);
- 安全性最佳实践:
- Cookie务必设置HttpOnly和Secure属性,防止XSS攻击;
- Session需设置合理的过期时间,分布式场景下使用Redis实现Session共享;
- Token使用非对称加密(如RS256),密钥不要硬编码,定期轮换;
- 敏感数据(如密码)禁止存储在Cookie或Token载荷中。
通过本文的讲解,相信大家对Cookie、Session、Token的底层逻辑、Java实现及适用场景有了全面的理解。在实际开发中,需灵活结合三者的优势,根据项目需求选择最优方案,同时注重安全性设计,避免常见的安全漏洞。
到此这篇关于Java视角下Cookie、Session、Token深度解析与实战的文章就介绍到这了,更多相关java cookie、session、token内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
IDEA maven加载依赖失败不展示Dependencies项的解决方案
低版本Maven手动新建工程时,可能因pom.xml中dependencyManagement依赖未定义版本号导致依赖丢失,下面给大家介绍IDEA maven加载依赖失败不展示Dependencies项的解决方案,感兴趣的朋友一起看看吧2025-07-07


最新评论