Session、Token 和 JWT 的区别对比
本文由AI生成:
1. 数据存储位置
Session (HttpSession)
- 数据存储在服务器内存中
- 客户端只保存
sessionId标识符 - 示例:
session.setAttribute(SESSION_KEY,msg)将数据存储在服务端
普通 Token (Redis存储)
- 数据存储在外部存储(如 Redis)中
- 客户端保存 token 标识符
- 示例:
redisUtils.set(token,msg)将数据存储在 Redis 中
JWT
- 数据编码在 token 本身中,存储在客户端
- 服务端不存储任何会话数据
- 示例:
jwtUtil.createToken(JWT_KEY,msg,10000)生成包含数据的完整 token
2. 状态管理方式
Session
- 有状态:服务端必须维护会话状态
- 依赖服务器内存或持久化存储
普通 Token
- 有状态:服务端需维护 token 与数据的映射关系
- 依赖外部存储系统
JWT
- 无状态:服务端不保存任何会话信息
- 自包含:所有必要信息都在 token 中
3. 扩展性和分布式支持
Session
- 扩展性差:多服务器部署时需要会话复制或粘性会话
- 难以实现真正的负载均衡
普通 Token
- 扩展性好:通过共享存储(如 Redis 集群)实现分布式支持
- 支持水平扩展
JWT
- 扩展性最佳:天然支持分布式无状态服务
- 无需共享存储即可实现会话共享
4. 安全性考虑
Session
- 安全性依赖
sessionId的保密性 - 需要防范 Session Fixation 攻击
普通 Token
- 安全性依赖 token 的随机性和保密性
- 需要防范 token 泄露和重放攻击
JWT
- 安全性依赖签名算法和密钥保护
- 需要防范 token 泄露,但无法中途撤销(除非设置黑名单)
5. 性能特征
Session
- 服务端内存消耗较大
- 访问速度快(内存访问)
普通 Token
- 服务端内存消耗小
- 需要网络访问外部存储(如 Redis)
JWT
- 服务端零内存消耗
- 需要 CPU 进行加解密运算
- 网络传输数据量相对较大
6. 生命周期管理
Session
- 服务端可主动销毁会话
- 支持精确的会话过期控制
普通 Token
- 服务端可主动删除 token
- 支持精确的过期时间控制
JWT
- 一旦签发难以中途撤销
- 过期时间编码在 token 中,无法动态调整
7. 适用场景
Session
- 传统的 Web 应用
- 用户量适中且不需要分布式部署的系统
- 需要存储大量会话数据的场景
普通 Token
- 分布式系统架构
- 需要在多个服务间共享认证状态
- 移动端应用后端
JWT
- 微服务架构
- 单点登录(SSO)系统
- API 认证和授权
- 跨域认证需求
8. 实现复杂度
Session
- 实现简单,Servlet 容器内置支持
普通 Token
- 需要引入外部存储组件
JWT
- 需要实现 token 的生成和验证逻辑
java
package com.easysession.controller;
import com.easysession.gwt.JWTUtil;
import com.easysession.redis.RedisUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;
/**
* @ClassName TestController
* @Description TODO
* @Author Administrator
* @Date 2023/12/16 16:22
*/
@RestController
public class TestController {
private static final String SESSION_KEY="session_key";
private static final String JWT_KEY ="jwt_key";
@Resource
private RedisUtils redisUtils;
@Resource
private JWTUtil<String> jwtUtil;
private boolean isEmpty(String value){
if(null==value||"".equals(value.trim())){
return true;
}
return false;
}
@RequestMapping("saveSessionInfo")
public String saveSessionInfo(HttpSession session,String msg){
if(isEmpty(msg)){
return "msg不能为空";
}
session.setAttribute(SESSION_KEY,msg);
return "保存session信息成功,sessionId:"+session.getId();
}
@RequestMapping("getSessionInfo")
public String getSessionInfo(HttpSession session){
return "获取的session信息为:"+session.getAttribute(SESSION_KEY);
}
@RequestMapping("saveByToken")
public String saveByToken(String msg){
if(isEmpty(msg)){
return "msg不能为空";
}
String token = UUID.randomUUID().toString();
redisUtils.set(token,msg);
return "保存token信息成功,token:"+token;
}
@RequestMapping("getByToken")
public String getByToken(String token){
if(isEmpty(token)){
return "token不能为空";
}
return "获取的token信息为:"+redisUtils.get(token);
}
@RequestMapping("saveByTokenWithCookie")
public String saveByTokenWithCookie(HttpServletResponse response,String msg){
if(isEmpty(msg)){
return "msg不能为空";
}
String token = UUID.randomUUID().toString();
redisUtils.set(token,msg);
Cookie cookie = new Cookie("token",token);
//cookie.setMaxAge(10);
response.addCookie(cookie);
return "保存token信息成功,token:"+token;
}
@RequestMapping("getByTokenWithCookie")
public String getByTokenWithCookie(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
if(cookies==null){
return "获取的token信息为:null";
}
String token = null;
for (Cookie cookie:cookies){
if("token".equals(cookie.getName())){
token = cookie.getValue();
break;
}
}
if(isEmpty(token)){
return "token不能为空";
}
return "获取的token信息为:"+redisUtils.get(token);
}
@RequestMapping("saveByJwt")
public String saveByJwt(String msg){
if(isEmpty(msg)){
return "msg不能为空";
}
String token =jwtUtil.createToken(JWT_KEY,msg,10000);
return "保存token信息成功,token:"+token;
}
@RequestMapping("getByJwt")
public String getByJwt(String token){
if(isEmpty(token)){
return "token不能为空";
}
String msg =jwtUtil.getTokenData(JWT_KEY,token,String.class);
return "获取jwt的信息是"+msg;
}
}
java
package com.easysession.gwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component("jwtUtil")
public class JWTUtil<T> {
private static final String SECRET = "test123456";
/**
* 签名生成
*
* @return
*/
public String createToken(String key, T data, Integer expireSeconds) {
String token = null;
try {
Date expiresAt = new Date(System.currentTimeMillis() + expireSeconds * 1000);
token = JWT.create()
.withClaim(key, JsonUtils.convertObj2Json(data))
.withExpiresAt(expiresAt)
.sign(Algorithm.HMAC256(SECRET));
} catch (Exception e) {
e.printStackTrace();
}
return token;
}
/**
* 签名验证
*
* @param token
* @return
*/
public <T> T getTokenData(String key, String token, Class<T> classz) {
try {
if (null == token || "".equals(token)) {
return null;
}
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
DecodedJWT jwt = verifier.verify(token);
String jsonData = jwt.getClaim(key).asString();
return JsonUtils.convertJson2Obj(jsonData, classz);
} catch (Exception e) {
return null;
}
}
}