Cookie 是存储在客户端的数据,Session 是存储在服务端的用户会话数据。Session 通常依赖 Cookie 来传递 SessionID。JWT 是一种无状态的令牌,包含用户信息和签名,服务端无需存储状态。
在分布式场景下,Session 需要解决共享问题(如 Redis 存储),而 JWT 天然支持分布式。但 JWT 无法主动失效,需要通过黑名单等机制实现。
一、为什么需要这些技术?
HTTP 协议是无状态的,服务器无法维持会话状态,无法识别两次请求是否来自同一个用户。为了实现用户身份识别和状态管理,诞生了 Cookie、Session、JWT 等技术。
二、Cookie 详解
2.1 什么是 Cookie?
Cookie 是服务器发送到浏览器并保存在客户端的一小段数据,浏览器在后续请求中会自动携带该 Cookie。
大小限制说明:每个域名的 Cookie 总大小不超过 4KB(包括所有 Cookie)。单个 Cookie 建议 < 4KB 以兼容所有浏览器。Chrome 实际允许单个 Cookie 超过 4KB,但为兼容性考虑应保持在限制内。
2.2 Cookie 工作原理

2.3 Cookie 核心属性
| 属性 | 说明 |
|---|---|
Name=Value |
Cookie 的键值对 |
Expires/Max-Age |
过期时间,不设置则为会话 Cookie |
Domain |
Cookie 生效的域名 |
Path |
Cookie 生效的路径 |
Secure |
仅在 HTTPS 下传输 |
HttpOnly |
禁止 JavaScript 访问,防 XSS |
SameSite |
防 CSRF 攻击(Strict/Lax/None) |
2.4 Java 操作 Cookie 示例
java
// 设置 Cookie
@GetMapping("/setCookie")
public String setCookie(HttpServletResponse response) {
Cookie cookie = new Cookie("userId", "12345");
cookie.setMaxAge(3600); // 1小时过期
cookie.setPath("/");
cookie.setHttpOnly(true); // 防止XSS
cookie.setSecure(true); // 仅HTTPS
response.addCookie(cookie);
return "Cookie设置成功";
}
// 读取 Cookie
@GetMapping("/getCookie")
public String getCookie(@CookieValue(value = "userId", defaultValue = "") String userId) {
return "userId: " + userId;
}
2.5 Cookie 的优缺点
优点:
- 实现简单,浏览器自动管理
- 减轻服务器存储压力
缺点:
- 容量限制(4KB)
- 安全性较低,容易被篡改
- 存在 CSRF 攻击风险
- 不支持跨域(跨域需额外配置)
2.6 Cookie 安全攻击与防御
| 攻击类型 | 描述 | 防御方式 |
|---|---|---|
| XSS | 通过脚本窃取 Cookie | 设置 HttpOnly 属性 |
| CSRF | 跨站请求伪造 | 设置 SameSite 属性 |
| Cookie Bombing | 填充大量 Cookie 导致请求头过大 | 服务端校验 Cookie 数量和大小 |
| 中间人攻击 | 传输过程被截获 | 设置 Secure 属性 + HTTPS |
2.7 SameSite 属性详解
浏览器兼容性 :Chrome 80+ 默认将 SameSite 设为
Lax,需注意跨站场景。
| 值 | 说明 | 适用场景 |
|---|---|---|
Strict |
完全禁止第三方 Cookie | 银行等高安全站点 |
Lax |
允许导航到目标网址的 GET 请求携带(默认值) | 大多数网站 |
None |
允许跨站发送,必须配合 Secure | 嵌入式应用、OAuth |
Java 中设置 SameSite(Servlet 4.0+):
java
// 方式一:通过 ResponseHeader
response.setHeader("Set-Cookie", "userId=12345; Path=/; HttpOnly; Secure; SameSite=Lax");
yaml
# 方式二:Spring Boot 配置 (application.yml)
server.servlet.session.cookie.same-site=lax
三、Session 详解
3.1 什么是 Session?
Session 是保存在服务器端的用户会话数据,通过 SessionID(通常存储在 Cookie 中)来标识用户。
3.2 Session 工作原理

3.3 Java 操作 Session 示例
java
// 设置 Session
@GetMapping("/setSession")
public String setSession(HttpServletRequest request) {
HttpSession session = request.getSession();
session.setAttribute("user", new User("张三", 25));
session.setMaxInactiveInterval(1800); // 30分钟过期
return "Session设置成功,ID: " + session.getId();
}
// 读取 Session
@GetMapping("/getSession")
public String getSession(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
User user = (User) session.getAttribute("user");
return "用户: " + user.getName();
}
return "Session不存在";
}
// 销毁 Session(登出)
@GetMapping("/logout")
public String logout(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
return "登出成功";
}
3.4 Session 的存储方式
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 内存存储 | 默认方式,存在JVM内存 | 单机应用 |
| 文件存储 | 序列化到磁盘(开销大,不推荐高并发) | 小型应用 |
| 数据库存储 | 存储到MySQL等 | 需持久化场景 |
| Memcached存储 | 分布式内存缓存 | 分布式系统 |
| Redis存储 | 分布式缓存,支持持久化 | 分布式系统首选 |
注意 :存入 Session 的对象需实现
Serializable接口(Redis/文件存储场景)。
3.5 分布式 Session 解决方案

分布式 Session 方案性能对比:
| 方案 | 性能影响 | 扩展性 | 成本 |
|---|---|---|---|
| Sticky Session | 负载均衡器压力大,单机故障影响大 | 差 | 低 |
| Session 复制 | 网络同步开销高 | 中 | 中 |
| Redis 集中存储 | 读写延迟低(毫秒级) | 高 | 中(需Redis集群) |
| JWT | 无存储开销,验证快(签名校验<1ms) | 高 | 低 |
Spring Session + Redis 配置:
java
// 1. 添加依赖
// spring-boot-starter-data-redis
// spring-session-data-redis
// 2. 配置
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
@Configuration
public class SessionConfig {
// Redis连接配置...
}
3.6 Session 安全攻击与防御
| 攻击类型 | 描述 | 防御方式 |
|---|---|---|
| Session Fixation | 攻击者固定 SessionID 诱导用户登录 | 登录后重置 SessionID(Spring Security 默认支持) |
| Session Hijacking | 窃取用户 SessionID | HTTPS + HttpOnly Cookie |
| Session 过期攻击 | 利用长期有效的 Session | 设置合理过期时间 + 滑动过期 |
登录后重置 SessionID(防 Session Fixation):
java
@PostMapping("/login")
public String login(HttpServletRequest request) {
// 验证用户...
// 登录成功后重置 SessionID,防止 Session Fixation 攻击
HttpSession oldSession = request.getSession(false);
if (oldSession != null) {
oldSession.invalidate();
}
HttpSession newSession = request.getSession(true);
newSession.setAttribute("user", authenticatedUser);
return "登录成功";
}
3.7 Session 在分布式环境下有什么问题?怎么解决?
问题: 用户请求可能被分发到不同服务器,而 Session 默认存储在单机内存中,导致 Session 不共享。
解决方案:
- Session 粘滞(Sticky Session):Nginx 配置 ip_hash,同一用户固定访问同一台服务器
- Session 复制:服务器之间同步 Session(Tomcat 集群支持)
- Session 集中存储 :使用 Redis 集中存储 Session(推荐)
- 使用 JWT:改为无状态认证,彻底解决问题
3.8 Session 的优缺点
优点:
- 数据存储在服务端,安全性高
- 可存储任意类型数据,无大小限制
缺点:
- 占用服务器内存
- 分布式环境需额外处理(Session共享)
- 依赖 Cookie 传递 SessionID
四、JWT 详解
4.1 什么是 JWT?
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全传输信息的紧凑的、自包含的令牌。
4.2 JWT 结构
JWT 由三部分组成,用 . 分隔:
erlang
Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuW8oOS4iSIsImlhdCI6MTUxNjIzOTAyMn0.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
① Header(头部)
json
{
"alg": "HS256", // 签名算法
"typ": "JWT" // 令牌类型
}
② Payload(载荷)
json
{
"sub": "1234567890", // 主题(用户ID)
"name": "张三", // 自定义数据
"iat": 1516239022, // 签发时间
"exp": 1516242622, // 过期时间
"role": "admin" // 自定义:用户角色
}
③ Signature(签名)
scss
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
)
4.3 JWT 工作原理

安全警告:Payload 是 Base64 编码而非加密,任何人都可解码查看。绝对不要存储密码、身份证号等敏感信息!
4.4 Java 操作 JWT 示例(使用 jjwt)
Maven 依赖:
xml
<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>
JwtUtil 工具类:
java
@Component
public class JwtUtil {
private static final String SECRET_KEY = "your-256-bit-secret-key-here-must-be-long";
private static final long EXPIRATION = 24 * 60 * 60 * 1000; // 24小时
// 生成 JWT
public String generateToken(String userId, String role) {
return Jwts.builder()
.setSubject(userId)
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()), SignatureAlgorithm.HS256)
.compact();
}
// 解析 JWT
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.build()
.parseClaimsJws(token)
.getBody();
}
// 验证 JWT
public boolean validateToken(String token) {
try {
parseToken(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
// 获取用户ID
public String getUserId(String token) {
return parseToken(token).getSubject();
}
}
登录接口示例:
java
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// 验证用户名密码(省略)
User user = userService.authenticate(request.getUsername(), request.getPassword());
if (user != null) {
String token = jwtUtil.generateToken(user.getId(), user.getRole());
return ResponseEntity.ok(Map.of("token", token));
}
return ResponseEntity.status(401).body("认证失败");
}
}
JWT 过滤器:
java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (jwtUtil.validateToken(token)) {
String userId = jwtUtil.getUserId(token);
// 设置认证信息到 SecurityContext
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(userId, null, getAuthorities(token));
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
chain.doFilter(request, response);
}
private Collection<? extends GrantedAuthority> getAuthorities(String token) {
String role = (String) jwtUtil.parseToken(token).get("role");
return Collections.singleton(new SimpleGrantedAuthority("ROLE_" + role));
}
}
4.5 JWT 的优缺点
优点:
- 无状态:服务端不需存储,天然支持分布式
- 跨语言:标准化格式,任何语言都可解析
- 自包含:载荷包含用户信息,减少数据库查询
- 支持跨域:可放在请求头中传递
缺点:
- 无法主动失效:一旦签发,在过期前无法作废(可用黑名单解决)
- 载荷暴露:Base64 编码而非加密,不要存敏感数据
- Token 较长:比 SessionID 占用更多带宽
4.6 JWT 安全攻击与防御
| 攻击类型 | 描述 | 防御方式 |
|---|---|---|
| 算法降级攻击 | 将 alg 设为 none 绕过验证 | 解析时强制指定算法 |
| 密钥爆破 | 暴力破解弱密钥 | 使用 256 位以上强密钥 |
| Token 泄露 | XSS/中间人攻击获取 Token | HTTPS + HttpOnly Cookie 存储 |
| Token 重放 | 截获 Token 重复使用 | 短过期时间 + Refresh Token |
4.7 如何防止 JWT 被盗用?
- 使用 HTTPS:防止传输过程被窃取
- 设置合理过期时间:建议 Access Token 15分钟~2小时
- Refresh Token 机制:短期 Access Token + 长期 Refresh Token
- 绑定客户端特征:IP、User-Agent 等
- 敏感操作二次验证:修改密码、支付等需再次输入密码
4.8 双 Token 机制(Access Token + Refresh Token)

流程说明:
- 登录成功 → 返回 Access Token(15分钟)+ Refresh Token(7天)
- 请求接口 → 携带 Access Token
- Access Token 过期 → 用 Refresh Token 换取新的 Access Token
- Refresh Token 过期 → 重新登录
优势:Access Token 短期有效降低泄露风险,Refresh Token 避免频繁登录。
注意:双 Token 机制通常用于 JWT 或 OAuth2,也可与 Session 结合使用。
4.9 JWT 主动失效解决方案
方案一:Token 黑名单
java
// 登出时将 Token 加入 Redis 黑名单
public void logout(String token) {
Claims claims = jwtUtil.parseToken(token);
long ttl = claims.getExpiration().getTime() - System.currentTimeMillis();
redisTemplate.opsForValue().set("blacklist:" + token, "1", ttl, TimeUnit.MILLISECONDS);
}
// 验证时检查黑名单
public boolean validateToken(String token) {
if (redisTemplate.hasKey("blacklist:" + token)) {
return false;
}
// 正常验证...
}
方案二:Token 版本号
java
// 用户表增加 token_version 字段
// JWT 中存储版本号,验证时比对数据库版本
// 修改密码时更新版本号,使旧 Token 失效
性能提示:高频验证场景下,黑名单查询会有 Redis 开销。可考虑使用布隆过滤器优化。
五、三者的区别
5.1 Cookie、Session、JWT 的区别
| 特性 | Cookie | Session | JWT |
|---|---|---|---|
| 存储位置 | 客户端 | 服务端 | 客户端 |
| 安全性 | 较低 | 高 | 中(需HTTPS) |
| 跨域支持 | 不支持 | 不支持 | 支持 |
| 分布式支持 | N/A | 需要额外处理 | 天然支持 |
| 服务器压力 | 无 | 占用内存 | 无 |
| 主动失效 | 可以 | 可以 | 不支持(需黑名单) |
| 数据大小 | ≤4KB | 无限制 | 建议≤8KB |
| 适用场景 | 简单状态保持 | 传统Web应用 | 前后端分离/微服务 |
5.2 Cookie 和 Session 的区别?
| 维度 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端浏览器 | 服务器端 |
| 安全性 | 较低,可被查看篡改 | 较高,数据在服务端 |
| 生命周期 | 可设置长期有效 | 默认会话结束失效 |
| 存储限制 | 4KB左右 | 理论无限制 |
| 关系 | 可独立使用 | 通常依赖Cookie存SessionID |
5.3 JWT 和 Session 如何选择?
| 场景 | 推荐方案 |
|---|---|
| 传统 MVC 单体应用 | Session |
| 前后端分离 | JWT |
| 微服务架构 | JWT |
| 需要频繁主动踢出用户 | Session |
| 移动端 APP | JWT |
| 对安全性要求极高 | Session + 二次验证 |
5.4 认证流程对比
Session 模式:
scss
登录 → 服务器创建Session → 返回SessionID(Cookie)
→ 请求携带Cookie → 服务器查询Session → 验证成功
JWT 模式:
markdown
登录 → 服务器生成JWT → 返回Token
→ 请求携带Token → 服务器验证签名+解析 → 验证成功
5.5 有无状态的本质区别

六、总结
| 技术 | 一句话总结 |
|---|---|
| Cookie | 客户端存储的小型文本,浏览器自动携带 |
| Session | 服务端存储用户状态,依赖 Cookie 传递 SessionID |
| JWT | 自包含的无状态令牌,天然支持分布式 |
使用建议:
- Cookie 务必设置
HttpOnly、Secure、SameSite - JWT 密钥要足够长(256位以上)且定期更换
- 敏感数据不要存储在 JWT 的 Payload 中
- 生产环境必须使用 HTTPS
- 合理设置 Token 过期时间
参考
- JWT: RFC 7519 - JSON Web Token
- Cookie: RFC 6265 - HTTP State Management Mechanism
- Spring Session: 官方文档
