2025-04-09 17:23:21 +08:00
|
|
|
|
下面从架构、扩展性、安全性、管理成本等几个维度,对 **Session** 和 **JWT** 进行对比,帮助你根据场景选择合适的方案。
|
2025-03-24 16:04:56 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
------
|
|
|
|
|
|
|
|
|
|
## 一、基本原理
|
2025-03-24 16:04:56 +08:00
|
|
|
|
|
2025-04-09 17:23:21 +08:00
|
|
|
|
| 特性 | Session | JWT(JSON Web Token) |
|
|
|
|
|
| -------- | ------------------------------------ | ----------------------------------------------- |
|
|
|
|
|
| 存储方式 | 服务端存储会话数据(如内存、Redis) | 客户端存储完整的令牌(通常在 Header 或 Cookie) |
|
|
|
|
|
| 标识方式 | 客户端持有一个 Session ID | 客户端持有一个自包含的 Token |
|
|
|
|
|
| 状态管理 | 有状态(Stateful),服务器要维护会话 | 无状态(Stateless),服务器不存会话 |
|
|
|
|
|
|
|
|
|
|
------
|
|
|
|
|
|
|
|
|
|
## 二、对比分析
|
|
|
|
|
|
|
|
|
|
### 1. 架构与扩展性
|
|
|
|
|
|
|
|
|
|
- **Session**
|
|
|
|
|
- 单体应用:内存中维护 Map<sessionId, userData>,简单易用。
|
|
|
|
|
- 分布式/集群:需要共享 Session(如 Redis、数据库、Sticky Session),增加运维成本。
|
|
|
|
|
- **JWT**
|
|
|
|
|
- 无状态:令牌自带用户信息及签名,服务器只需校验签名即可,无需存储。
|
|
|
|
|
- 分布式友好:各节点只要共享签名密钥(或公钥),即可校验,无需集中存储。
|
|
|
|
|
|
|
|
|
|
### 2. 性能
|
|
|
|
|
|
|
|
|
|
- **Session**
|
|
|
|
|
- 每次请求都要从存储(内存/Redis/DB)读取会话数据,IO 成本或网络开销。
|
|
|
|
|
- 并发高时,集中式 Session 存储可能成为瓶颈。
|
|
|
|
|
- **JWT**
|
|
|
|
|
- 校验签名(HMAC 或 RSA)为 CPU 操作,无网络开销,性能开销较小。
|
|
|
|
|
- 但 Token 通常更大(包含多段 Base64),每次请求都要传输,带宽略增。
|
|
|
|
|
|
|
|
|
|
### 3. 安全性
|
|
|
|
|
|
|
|
|
|
- **Session**
|
|
|
|
|
- 会话数据保存在服务器端,客户端只能拿到 Session ID,敏感数据不暴露。
|
|
|
|
|
- 可在服务器端随时销毁或更新 Session(强制登出、权限变更即时生效)。
|
|
|
|
|
- **JWT**
|
|
|
|
|
- 令牌自包含所有声明(claims),如果存敏感数据需加密(JWE),否则仅签名(JWS)也可能泄露信息。
|
|
|
|
|
- 无法主动撤销(除非做黑名单),需要控制有效期并结合“刷新令牌”机制。
|
|
|
|
|
|
|
|
|
|
### 4. 可控性与管理
|
|
|
|
|
|
|
|
|
|
- **Session**
|
|
|
|
|
- **可控性强**:服务器可随时作废 Session,适合需要即时注销、权限动态调整的场景。
|
|
|
|
|
- 过期策略灵活:可按用户、按应用统一配置。
|
|
|
|
|
- **JWT**
|
|
|
|
|
- **可控性弱**:Token 一旦签发,在到期前无法从服务器强制失效(除非额外维护黑名单)。
|
|
|
|
|
- 需要设计“短生命周期 + 刷新令牌”模式,增加实现复杂度。
|
|
|
|
|
|
|
|
|
|
### 5. 跨域与移动端
|
|
|
|
|
|
|
|
|
|
- **Session**
|
|
|
|
|
- 依赖 Cookie(同源策略),跨域或移动端(原生 App)使用受限。
|
|
|
|
|
- 跨域时需配合 CORS + `withCredentials`,且浏览器必须支持并开启 Cookie。
|
|
|
|
|
- **JWT**
|
|
|
|
|
- 与 HTTP 协议无关,既可放在 Authorization 头,也可放在 URL、LocalStorage,移动端/第三方客户端更友好。
|
|
|
|
|
- 只要客户端能发送 HTTP Header,就能携带 Token。
|
|
|
|
|
|
|
|
|
|
### 6. 实现复杂度
|
|
|
|
|
|
|
|
|
|
- **Session**
|
|
|
|
|
- 框架通常开箱即用(如 Spring Session),开发者只需开启即可。
|
|
|
|
|
- 自动处理过期、失效,管理简单。
|
|
|
|
|
- **JWT**
|
|
|
|
|
- 需要设计签名算法、密钥管理、过期策略、刷新机制、黑名单等。
|
|
|
|
|
- 容易因配置不当造成安全漏洞(算法降级、密钥泄露、Token 劫持等)。
|
|
|
|
|
|
|
|
|
|
------
|
|
|
|
|
|
|
|
|
|
## 三、何时选用
|
|
|
|
|
|
|
|
|
|
| 场景类型 | 推荐方案 | 原因 |
|
|
|
|
|
| --------------------------- | -------- | ---------------------------------------------------------- |
|
|
|
|
|
| 单体 Web 应用、后台管理系统 | Session | 简单、可控、安全性高,框架支持完善。 |
|
|
|
|
|
| 分布式微服务、无状态 API | JWT | 无需集中存储,易扩展;移动端/第三方客户端友好。 |
|
|
|
|
|
| 高度安全、需即时失效场景 | Session | 可随时在服务器端销毁会话,确保强制登出或权限变更即时生效。 |
|
|
|
|
|
| 跨域或多端(Web + App) | JWT | Token 可在多种客户端轻松传递,无需依赖浏览器 Cookie。 |
|
|
|
|
|
|
|
|
|
|
------
|
|
|
|
|
|
|
|
|
|
## 四、示例代码
|
|
|
|
|
|
|
|
|
|
### Session 示例(Spring Boot)
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
// 登录时创建 Session
|
|
|
|
|
@PostMapping("/login")
|
|
|
|
|
public String login(HttpServletRequest req, @RequestParam String user, @RequestParam String pass) {
|
|
|
|
|
if (authService.verify(user, pass)) {
|
|
|
|
|
req.getSession().setAttribute("userId", user);
|
|
|
|
|
return "登录成功";
|
|
|
|
|
}
|
|
|
|
|
return "登录失败";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 受保护资源
|
|
|
|
|
@GetMapping("/profile")
|
|
|
|
|
public User profile(HttpServletRequest req) {
|
|
|
|
|
String userId = (String) req.getSession().getAttribute("userId");
|
|
|
|
|
return userService.findById(userId);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### JWT 示例(Spring Boot + jjwt)
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
// 生成 Token
|
|
|
|
|
@PostMapping("/login")
|
|
|
|
|
public String login(@RequestParam String user, @RequestParam String pass) {
|
|
|
|
|
if (authService.verify(user, pass)) {
|
|
|
|
|
return Jwts.builder()
|
|
|
|
|
.setSubject(user)
|
|
|
|
|
.setExpiration(new Date(System.currentTimeMillis() + 3600_000))
|
|
|
|
|
.signWith(SignatureAlgorithm.HS256, secretKey)
|
|
|
|
|
.compact();
|
|
|
|
|
}
|
|
|
|
|
throw new UnauthorizedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 过滤器中校验
|
|
|
|
|
@Override
|
|
|
|
|
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {
|
|
|
|
|
String token = req.getHeader("Authorization");
|
|
|
|
|
if (token != null && token.startsWith("Bearer ")) {
|
|
|
|
|
String user = Jwts.parser().setSigningKey(secretKey)
|
|
|
|
|
.parseClaimsJws(token.substring(7))
|
|
|
|
|
.getBody().getSubject();
|
|
|
|
|
// 将 user 存入 SecurityContext …
|
|
|
|
|
}
|
|
|
|
|
chain.doFilter(req, res);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
------
|
|
|
|
|
|
|
|
|
|
## 五、结论
|
|
|
|
|
|
|
|
|
|
- **Session**:上手简单、安全可控,适合绝大多数传统 Web 应用。
|
|
|
|
|
- **JWT**:更灵活、易扩展,适合分布式架构、多端场景,但需要更复杂的设计与安全防护。
|
|
|
|
|
|
|
|
|
|
根据你的项目架构、团队经验和安全需求,选择最合适的方案即可。
|