jwt与token+redis,哪种方案更好用?
前言
今天我们来聊聊一个非常经典的话题:JWT和Token+Redis两种认证方案,到底哪种更好用?
有些小伙伴在工作中可能会纠结于选择哪种方案,今天我就从底层原理到实际应用,给大家做一个全面的剖析。
希望对你会有所帮助。
一、认证与授权
在深入讨论之前,我们先明确两个基本概念:
认证(Authentication):你是谁?验证用户身份的过程
授权(Authorization):你能做什么?验证用户权限的过程
无论是JWT还是Token+Redis,都是用来解决这两个问题的技术方案。
二、JWT方案
2.1 JWT是什么?
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为JSON对象。JWT由三部分组成:
header.payload.signature
Header:包含令牌类型和签名算法
Payload:包含声明(用户信息、过期时间等)
Signature:用于验证消息在传输过程中没有被篡改
2.2 JWT的工作流程
让我们通过一个完整的登录流程来理解JWT的工作原理:
2.3 JWT的Java实现示例
下面是一个简单的JWT工具类实现:
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;public class JwtUtil {// 密钥,实际项目中应从配置中读取private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);// 过期时间:2小时private static final long EXPIRATION_TIME = 2 * 60 * 60 * 1000;/*** 生成JWT*/public static String generateToken(String userId, String username, List<String> roles) {return Jwts.builder().setSubject(userId).claim("username", username).claim("roles", roles).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)).signWith(key).compact();}/*** 验证并解析JWT*/public static Claims parseToken(String token) {try {return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();} catch (ExpiredJwtException e) {thrownew RuntimeException("Token已过期", e);} catch (JwtException e) {thrownew RuntimeException("Token无效", e);}}/*** 刷新Token*/public static String refreshToken(String token) {Claims claims = parseToken(token);return generateToken(claims.getSubject(), claims.get("username", String.class), claims.get("roles", List.class));}
}
2.4 JWT的优点和缺点
优点:
无状态:服务端不需要存储会话信息
跨域友好:适合分布式系统和微服务架构
自包含:令牌中包含所有必要信息
扩展性好:可以轻松添加自定义声明
缺点:
无法主动失效:一旦签发,在到期前一直有效
令牌大小:包含的信息越多,令牌越大
安全性依赖:完全依赖签名,密钥泄露后果严重
三、Token+Redis方案
3.1 Token+Redis是什么?
Token+Redis方案使用随机生成的令牌作为用户会话的标识,将会话数据存储在Redis中。
这种方案本质上是有状态的,服务端需要维护会话状态。
3.2 Token+Redis的工作流程
3.3 Token+Redis的Java实现示例
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.UUID;
import java.util.concurrent.TimeUnit;@Component
public class RedisSessionManager {private final RedisTemplate<String, Object> redisTemplate;// 会话过期时间:2小时private static final long SESSION_EXPIRE_TIME = 2 * 60 * 60;public RedisSessionManager(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}/*** 创建会话*/public String createSession(User user) {String token = generateToken();SessionInfo sessionInfo = new SessionInfo(user.getId(), user.getUsername(), user.getRoles());redisTemplate.opsForValue().set(getRedisKey(token), sessionInfo, SESSION_EXPIRE_TIME, TimeUnit.SECONDS);return token;}/*** 获取会话信息*/public SessionInfo getSession(String token) {return (SessionInfo) redisTemplate.opsForValue().get(getRedisKey(token));}/*** 删除会话*/public void deleteSession(String token) {redisTemplate.delete(getRedisKey(token));}/*** 刷新会话有效期*/public void refreshSession(String token) {redisTemplate.expire(getRedisKey(token), SESSION_EXPIRE_TIME, TimeUnit.SECONDS);}/*** 生成随机token*/private String generateToken() {return UUID.randomUUID().toString().replace("-", "");}/*** 获取Redis key*/private String getRedisKey(String token) {return "session:" + token;}/*** 会话信息类*/@Data@AllArgsConstructorpublic static class SessionInfo {private String userId;private String username;private List<String> roles;private long createTime;public SessionInfo(String userId, String username, List<String> roles) {this.userId = userId;this.username = username;this.roles = roles;this.createTime = System.currentTimeMillis();}}
}
3.4 Token+Redis的优点和缺点
优点:
主动控制:可以随时使特定令牌失效
信息量小:令牌只是一个标识符,不会太大
灵活性高:可以存储复杂的会话状态
安全性好:令牌泄露可以立即撤销
缺点:
有状态:服务端需要存储会话信息
Redis依赖:Redis成为单点故障源
网络开销:每次请求都需要查询Redis
扩展性挑战:需要处理Redis集群和数据同步
四、深度对比分析
4.1 性能对比
从性能角度,两种方案有显著差异:
方面 | JWT | Token+Redis |
---|---|---|
认证速度 | 快(本地验证) | 慢(需要Redis查询) |
网络开销 | 小 | 大(每次请求都需要访问Redis) |
服务端压力 | 小 | 大(Redis需要处理大量查询) |
扩展成本 | 低 | 高(需要维护Redis集群) |
4.2 安全性对比
安全性是认证方案的核心考量因素:
JWT安全性考虑:
密钥管理:签名密钥需要严格保护,定期轮换
令牌泄露:无法主动失效,只能等待自动过期
算法选择:需要选择安全的签名算法(如HS256、RS256)
Token+Redis安全性考虑:
Redis安全:需要保证Redis实例的安全性
令牌随机性:令牌必须足够随机,防止猜测
传输安全:需要HTTPS防止令牌被窃听
4.3 适用场景对比
不同的业务场景适合不同的方案:
适合JWT的场景:
分布式系统和微服务架构
需要跨域认证的单页应用(SPA)
无状态API服务
移动应用后端
适合Token+Redis的场景:
需要精细控制会话的企业应用
需要实时吊销权限的系统
会话信息复杂的传统Web应用
对安全性要求极高的金融系统
五、混合方案
有些小伙伴在工作中可能会想:能不能结合两种方案的优点?
答案是肯定的!
下面介绍一种混合方案:
5.1 短期JWT + Redis黑名单
这种方案使用短期有效的JWT,配合Redis黑名单实现主动注销:
public class HybridAuthManager {private final JwtUtil jwtUtil;private final RedisTemplate<String, Object> redisTemplate;// JWT短期有效期:15分钟private static final long SHORT_EXPIRATION = 15 * 60 * 1000;// 刷新令牌有效期:7天private static final long REFRESH_EXPIRATION = 7 * 24 * 60 * 60 * 1000;/*** 生成访问令牌和刷新令牌*/public AuthResponse generateTokenPair(User user) {// 生成短期访问令牌String accessToken = jwtUtil.generateToken(user.getId(), user.getUsername(), user.getRoles(), SHORT_EXPIRATION);// 生成长期刷新令牌String refreshToken = UUID.randomUUID().toString();// 存储刷新令牌到RedisstoreRefreshToken(refreshToken, user.getId());returnnew AuthResponse(accessToken, refreshToken);}/*** 刷新访问令牌*/public String refreshAccessToken(String refreshToken) {// 验证刷新令牌有效性String userId = validateRefreshToken(refreshToken);if (userId == null) {thrownew RuntimeException("刷新令牌无效");}// 获取用户信息User user = userService.getUserById(userId);// 生成新的访问令牌return jwtUtil.generateToken(user.getId(), user.getUsername(), user.getRoles(), SHORT_EXPIRATION);}/*** 注销令牌*/public void logout(String accessToken, String refreshToken) {// 将访问令牌加入黑名单(剩余有效期内)Claims claims = jwtUtil.parseToken(accessToken);long expiration = claims.getExpiration().getTime() - System.currentTimeMillis();if (expiration > 0) {redisTemplate.opsForValue().set("blacklist:" + accessToken, "logout", expiration, TimeUnit.MILLISECONDS);}// 删除刷新令牌if (refreshToken != null) {redisTemplate.delete("refresh_token:" + refreshToken);}}/*** 验证令牌是否在黑名单中*/public boolean isTokenBlacklisted(String token) {return redisTemplate.hasKey("blacklist:" + token);}
}
5.2 混合方案工作流程
六、实际项目选型建议
根据我多年的工作经验,给大家一些实用的选型建议:
6.1 选择JWT当以下情况成立时:
系统是分布式架构,需要无状态认证。
需要支持跨域认证(如多个前端应用共享后端)。
API消费者主要是第三方应用或移动端。
团队有能力管理好密钥和令牌安全。
6.2 选择Token+Redis当以下情况成立时:
系统是单体或少量服务的架构。
需要精细的会话控制和实时权限管理。
有专业的运维团队维护Redis集群。
对安全性要求极高,需要即时吊销能力。
6.3 选择混合方案当以下情况成立时:
既需要JWT的无状态特性,又需要主动注销能力。
系统对用户体验要求高(避免频繁登录)。
有能力处理稍复杂的令牌管理逻辑。
需要平衡安全性和便利性。
总结
通过上面的详细分析,JWT和token+redis这两种方案,各有优缺点和适用场景。
我们可以得出以下结论:
没有绝对的最好方案:只有最适合具体业务场景的方案。
JWT优势在无状态和扩展性:适合分布式系统和API优先的架构。
Token+Redis优势在控制和灵活性:适合需要精细会话管理的企业应用。
混合方案取长补短:适合大多数现代Web应用。
有些小伙伴在工作中可能会盲目追求技术的新颖性,或者过度设计认证方案。
我的建议是:从实际业务需求出发,选择最简单可靠的方案。
对于大多数应用来说,我推荐采用混合方案:
使用短期JWT保证API的无状态特性。
使用刷新令牌机制优化用户体验。
使用Redis黑名单提供主动注销能力。
使用HTTPS和严格的密钥管理保证安全性。
无论选择哪种方案,都要记住:安全不是一个功能,而是一个过程。
希望这篇文章能帮助大家在技术选型时做出更明智的决策。