还分不清登录状态?session?cookie?token?进来看看!
本文深入解析了Session、Cookie和Token三者的核心原理与区别。Cookie是浏览器存储的文本载体,Session依赖服务端存储会话数据,而Token(如JWT)则是自包含的无状态字符串。关键区别在于:Session需服务端存储且存在跨域问题,Token天然支持分布式但无法中途作废。Session-Cookie机制易受CSRF攻击,需配合Token校验或SameSite防护;Token
【硬核干货】Session、Cookie、Token 区别与实战:一篇讲透
下面我用清晰的方式,把 Session、Cookie、Token 三者的关系、区别、适用场景一次性讲透。你看完就能直接用在面试和实际项目里。
一、三者的本质与核心原理
1. Cookie
- 本质:浏览器本地存储的一小段文本。
- 核心特性:由服务器通过
Set-Cookie响应头下发;浏览器在同域请求中会自动携带;支持Domain、Path、Expires/Max-Age、HttpOnly(防 JS 读取)、Secure(仅 HTTPS 传输)、SameSite(防跨站携带)等属性限制。 - 作用:只是一个存储和传递的载体,本身不负责登录状态逻辑。默认仅同域名自动携带,遵循严格的同源策略。
2. Session
- 本质:服务器端保存的用户会话数据。
- 核心结构:服务器生成唯一
sessionId,在内存/Redis/数据库中维护映射sessionId → { userId, 权限, 登录时间… },并将sessionId放入 Cookie(默认键名JSESSIONID)发给浏览器。 - 工作方式:浏览器带 Cookie → 服务器拿到
sessionId→ 查服务器存储 → 认出你是谁。会话过期或注销时,服务端销毁 Session,客户端清空 Cookie。
3. Token(以最常见的 JWT 为例)
- 本质:一串经过签名、自包含身份信息的字符串。
- 核心结构:
Header.Payload.Signature(头.载荷.签名)。载荷(Payload)里直接包含userId、过期时间、角色等声明。 - 工作方式:无状态。服务器不需要存任何会话数据。标准传递位置为请求头
Authorization: Bearer <token>。服务器只做两件事:验证签名是否合法 + 检查是否过期。
二、核心区别对比表
| 对比维度 | Session-Cookie 机制 | Token(JWT)机制 |
|---|---|---|
| 状态保存位置 | 服务器端(内存/Redis/DB) | 无状态,信息存在 Token 本身 |
| 存储依赖 | 强依赖服务端存储,需考虑集群共享 | 不依赖服务端存储,天然水平扩展 |
| 跨域/分布式支持 | 麻烦,需配置共享 Cookie 或粘滞会话 | 天然支持跨域、多服务、微服务网关 |
| 客户端传递方式 | 浏览器自动携带 Cookie | 前端手动放入 Header 或 URL 参数 |
| CSRF 风险 | 较高(浏览器自动带,需额外防护) | 极低(不会自动带,需前端显式附加) |
| 移动端/小程序支持 | 不友好(需手动管理 Cookie 容器) | 非常友好(直接存字符串,随请求附加) |
| 过期与踢人控制 | 服务器可随时强制下线,立即生效 | 签发后无法中途作废(需黑名单或双 Token 机制) |
| 性能开销 | 每次请求需查存储(IO 开销) | 仅做签名验签(CPU 计算,无 IO) |
三、完整登录流程对比
1. Session-Cookie 登录流程
- 客户端第一次请求服务端,服务端验证账号密码后生成唯一
sessionId,存入服务端内存/Redis。 - 响应头自动写入
Set-Cookie,将sessionId下发到浏览器本地存储。 - 后续同域请求,浏览器自动携带对应 Cookie。
- 服务端拦截请求,提取 Cookie 中的
sessionId,匹配服务端会话数据,识别用户身份。 - 用户登出或会话超时,服务端销毁 Session,客户端清除 Cookie。
2. Token 登录流程
- 用户输入账号密码发起登录请求。
- 服务器验证通过,将用户信息、签发时间、过期时间打包,用密钥签名生成 JWT。
- 服务器将 Token 返回给前端(通常在响应体 JSON 中)。
- 前端将 Token 存入
localStorage、Vuex/Pinia 或 App 安全存储区。 - 后续请求前端通过拦截器手动附加到请求头:
Authorization: Bearer <token>。 - 网关或业务服务验签 + 查有效期,合法则放行。
四、CSRF 攻击原理、风险与防护方案
1. 为什么 Session-Cookie 机制天然高危
跨站请求伪造(CSRF)利用的是浏览器自动携带同源 Cookie的特性。攻击者无需窃取 Cookie,只需诱导已登录用户在恶意页面触发请求,浏览器就会自动带上合法 SessionID。服务端仅校验 Session 合法性,不校验请求是否由用户主动发起,导致攻击成功。
2. 攻击完整流程
- 用户正常登录业务网站 A,浏览器保存 A 的 Cookie(含 SessionID)。
- 用户未登出 A,打开恶意网站 B。
- B 页面构造隐藏请求(表单/图片/iframe 接口请求),目标地址指向 A 的敏感接口。
- 浏览器发起请求时,自动携带网站 A 的 Cookie。
- 服务端拿到合法 SessionID,误认为是用户本人操作,执行转账、改密等敏感操作。
3. 主流防护方案
- Token 校验(最常用):后端下发随机 CSRF-Token,前端每次敏感请求手动携带(Header 或表单参数),后端二次校验。恶意跨站请求无法获取页面内的 Token,直接拦截。
- SameSite Cookie 属性:配置
SameSite=Strict(仅同站携带)或SameSite=Lax(默认,限制跨域 GET)。从根源限制跨域自动传 Cookie。 - 请求来源校验:校验
Origin/Referer请求头,拒绝非法跨域来源(辅助防护)。 - 敏感操作二次验证:关键接口增加验证码、短信验证或密码二次确认。
五、跨域与分布式集群下的痛点与解决方案
1. 跨域场景痛点
Cookie 遵循同源策略(协议+域名+端口一致)。前端 a.com 调用后端 api.b.com 时,浏览器默认不携带 b.com 的 Cookie,Session 直接失效。即使配置 CORS,不开启凭证模式也无法传递 Cookie。
跨域 Cookie 必要配置:
- 后端 CORS 允许:
Access-Control-Allow-Credentials: true - 前端请求开启凭证:浏览器
xhr.withCredentials = true或 AxioswithCredentials: true - Cookie 必须配置:
SameSite=None; Secure - 禁止配置
Access-Control-Allow-Origin: *,必须明确指定具体域名。
2. 分布式/集群部署 Session 共享问题
传统 Session 存在单机内存,负载均衡转发请求到不同服务器会导致会话丢失(用户请求 A 生成 Session,第二次请求被分到 B,B 无该 Session,强制掉线)。
分布式 Session 解决方案:
- 中心化存储(主流):将 Session 统一存入 Redis/数据库,所有业务节点共享同一份会话数据。无论请求转发到哪台机器,均能通过
sessionId查询会话。Spring Boot 可通过@EnableRedisHttpSession一键集成。 - IP 哈希负载均衡:根据客户端 IP 固定转发到同一台服务器。缺点:单点故障风险高、扩容不灵活,仅适用于小众场景。
- Cookie 序列化存储(Client Session):将用户会话加密后直接存在 Cookie,服务端无状态。本质已接近 JWT 思想。
六、移动端与小程序的兼容性困境
1. 原生 App(Android/iOS)
原生 App 无浏览器同源 Cookie 自动管理机制,不会自动存储、自动携带 Cookie。传统 Session-Cookie 方案在 App 中需手动管理 Cookie 容器,开发成本高且兼容性差。
2. 微信/支付宝/抖音小程序
小程序运行环境为独立渲染引擎,非标准浏览器。默认不自动持久化、不自动携带第三方 Cookie。request 请求不会像浏览器一样自动保存 Set-Cookie,跨域名业务下原生 Session-Cookie 方案直接失效。
3. 兼容改造结论
小程序/移动端业务应淘汰 Cookie-Session,优先使用 Token 方案。Token 放入请求头 Authorization,前端手动携带,全端统一,彻底摆脱环境兼容问题。若强制使用 Cookie,需开启 withCredentials: true 并配置 SameSite=None; Secure,但限制繁琐,生产环境极不推荐。
七、优缺点对比与架构选型指南
Session-Cookie
优点:服务器可随时强制踢人、修改权限;逻辑成熟稳定;对前端透明。
缺点:分布式需做共享;跨域配置繁琐;CSRF 风险高;移动端支持差。
适用场景:传统单体网站、企业后台管理系统(B/S 架构);对在线状态强管控、权限频繁变更的系统。
Token(JWT)
优点:无状态,任意加机器;天然跨域,契合微服务与 API 开放平台;移动端/小程序/SPA 极其友好;无 CSRF 风险。
缺点:签发后无法直接作废(需黑名单或 Access+Refresh 双机制);载荷不宜过大;前端需处理存储与刷新逻辑。
适用场景:前后端分离项目、微服务架构、API 网关鉴权;App、小程序、H5 跨端项目;第三方授权登录。
架构选型总结:
- 传统单体 Web 项目(PC 浏览器):可用 Cookie-Session,配合
SameSite+ CSRF-Token。 - 小程序 / App / 前后端分离 / 跨域项目:统一使用 JWT / 自定义 Token。
- 分布式集群 Web 项目:保留 Session 模式 → 改造为 Redis 共享 Session。
- 高安全敏感系统:禁止单纯依赖 Cookie 会话,必须双校验:Token + 接口签名 + 来源校验。
八、Spring Boot 核心实战代码(开箱即用)
注意:以下代码基于 Spring Boot 2.x。若你使用 Spring Boot 3.x,请将代码中的 import javax.* 替换为 import jakarta.*,其余逻辑完全一致。
共用依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1. Session-Cookie 版(最简可测)
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
@RestController
@RequestMapping("/session")
public class SessionController {
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password, HttpSession session) {
if ("admin".equals(username) && "123456".equals(password)) {
session.setAttribute("loginUser", username);
// 生产环境建议配置:session.setMaxInactiveInterval(1800); // 30分钟过期
return "登录成功,SessionId:" + session.getId();
}
return "账号或密码错误";
}
@GetMapping("/userInfo")
public String userInfo(HttpSession session) {
Object user = session.getAttribute("loginUser");
return user != null ? "当前登录用户:" + user : "未登录,请先访问 /session/login";
}
@PostMapping("/logout")
public String logout(HttpSession session) {
session.invalidate(); // 立即销毁会话
return "已安全登出";
}
}
测试方式:浏览器或 Postman 访问 /login 后,直接访问 /userInfo,浏览器会自动携带 Cookie,无需额外操作。
2. JWT Token 版(标准 Bearer 规范)
<!-- JWT 依赖 -->
<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>
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
@Component
public class JwtUtil {
// 生产环境务必放配置文件或密钥管理服务,至少 32 字节
private static final String SECRET = "mySecretKey123456789012345678901234";
private static final long EXPIRE_MS = 1000 * 60 * 30; // 30分钟
private Key getKey() {
return Keys.hmacShaKeyFor(SECRET.getBytes());
}
public String createToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE_MS))
.signWith(getKey(), SignatureAlgorithm.HS256)
.compact();
}
public String parseSubject(String token) {
return Jwts.parserBuilder()
.setSigningKey(getKey())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/token")
public class TokenController {
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
if ("admin".equals(username) && "123456".equals(password)) {
return jwtUtil.createToken(username);
}
return "账号或密码错误";
}
@GetMapping("/userInfo")
public String userInfo(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return "未携带合法 Token,格式应为: Authorization: Bearer <token>";
}
String token = authHeader.substring(7);
try {
String username = jwtUtil.parseSubject(token);
return "当前登录用户:" + username;
} catch (ExpiredJwtException e) {
return "Token 已过期,请重新登录";
} catch (JwtException e) {
return "Token 签名无效或已被篡改";
}
}
}
测试方式:
POST /token/login获取 Token 字符串。GET /token/userInfo,Header 添加:Authorization: Bearer 你的Token字符串。
九、面试高频考点与生产避坑指南
| 面试官常问 | 标准回答与生产实践 |
|---|---|
| JWT 被泄露了怎么办? | 缩短 Access Token 有效期(如 15 分钟);引入 Refresh Token 机制(存 Redis,可主动撤销);敏感操作要求二次验证(密码/短信)。 |
| Cookie 和 Token 能一起用吗? | 可以。现代架构常用 HttpOnly + Secure + SameSite=Strict 的 Cookie 存 Refresh Token,前端 JS 只拿 Access Token 放内存,兼顾安全与体验。 |
| 微服务怎么统一鉴权? | 网关层(Spring Cloud Gateway / Nginx)统一验签 JWT,解析出用户信息后放入请求头(如 X-User-Id),透传给下游业务服务,业务服务零鉴权逻辑。 |
| Token 太大影响性能? | Payload 只放核心标识(userId, roles),权限详情去 Redis/DB 按需查询。JWT 不是数据库,严禁塞入大量业务数据。 |
| Session 共享怎么做? | 使用 Spring Session + Redis。配置 @EnableRedisHttpSession,底层自动将 HttpSession 代理到 Redis,业务代码零改动,无缝支持集群。 |
十、总结
Cookie 是运输工具,负责凭证传递;Session 是服务器存的账本,负责有状态管理;Token 是自带签名、防伪的通行证,负责无状态鉴权。
- 传统后台与强管控系统:优先选择
Session + Redis 共享。 - 前后端分离、微服务、多端统一项目:优先选择
JWT + Refresh Token机制。 - 无论采用哪种方案,生产环境必须遵循三条底线:传输全程走 HTTPS;敏感接口配置防重放与限流策略;核心认证逻辑必须结合业务场景做二次校验与安全加固。
更多推荐



所有评论(0)