6.0 KiB
6.0 KiB
下面从架构、扩展性、安全性、管理成本等几个维度,对 Session 和 JWT 进行对比,帮助你根据场景选择合适的方案。
一、基本原理
特性 | 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)
// 登录时创建 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)
// 生成 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:更灵活、易扩展,适合分布式架构、多端场景,但需要更复杂的设计与安全防护。
根据你的项目架构、团队经验和安全需求,选择最合适的方案即可。