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
@AllArgsConstructor
public 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();
// 存儲刷新令牌到Redis
storeRefreshToken(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和嚴格的密鑰管理保證安全性。
無論選擇哪種方案,都要記?。喊踩皇且粋€功能,而是一個過程。
希望這篇文章能幫助大家在技術選型時做出更明智的決策。





























