雙Token實現用戶登錄身份認證-Java版
1. 設計方案
方案概述:
- Access Token: 短期有效的JWT,包含
用戶ID
、設備ID
、token版本號
。 - Refresh Token: 長期有效的令牌,存儲于Redis,關聯用戶信息、
設備ID
及token版本號
,用于刷新Access Token
。 - 設備綁定:
Token
與設備ID
綁定,防止跨設備使用。 - 安全性: 用戶修改關鍵信息時遞增
token版本號
,使舊Token
失效。
整個流程大致如下:
- 用戶登錄,提供
手機號
、密碼
、設備ID
。 - 服務端驗證通過后,生成
access token
(包含userId
、deviceId
、tokenVersion
)和refresh token
。 refresh token
存儲到Redis,key為refresh_token: + refreshToken
,值為userId
、deviceId
、tokenVersion
。- 客戶端保存
雙token
,并在請求時攜帶access token
和設備ID
。 - 攔截器驗證
access token
的有效性,包括簽名、過期時間、設備ID
匹配、tokenVersion
是否最新。 access token
過期后,客戶端使用refresh token
和設備ID
請求刷新。- 服務端驗證
refresh token
是否存在,設備ID
是否匹配,tokenVersion
是否一致,若通過則生成新的access token
,Redis中的refresh token
不變。 - 用戶修改信息時,更新
tokenVersion
,使所有舊的refresh token
和access token
失效。 - 不同設備的登錄生成不同的
refresh token
,設備間無法混用,,同時減少token
泄露帶來的后續風險。 - 用戶退出時,刪除Redis中對應得
refresh token
。
針對各個模塊的代碼實現:
- JWT工具類:處理生成和解析
token
,包含tokenVersion
。 - 登錄接口:生成
雙token
,存儲refresh token
到Redis。 - 刷新接口:處理
refresh token
,生成新的access token
。 - 攔截器:驗證
access token
和設備
信息。 - 用戶修改信息:更新
tokenVersion
。
2. 代碼實現
2.1 數據庫用戶信息表結構
2.2 JWT工具類
@Getter
@Component
public class AuthJwtUtil {@Value("${auth.jwt.secretKey}")private String secretKey;@Value("${auth.jwt.access.expiration}")private long accessExpirationMs;@Value("${auth.jwt.refresh.expiration}")private long refreshExpirationMs;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 生成訪問令牌accessToken* @param userId* @param deviceId* @param tokenVersion* @return*/public String generateAccessToken(Long userId, String deviceId, Integer tokenVersion) {return Jwts.builder().claim("userId", userId).claim("deviceId", deviceId).claim("tokenVersion", tokenVersion).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + accessExpirationMs)).signWith(SignatureAlgorithm.HS256, secretKey.getBytes(StandardCharsets.UTF_8)).compact();}/*** 解析訪問令牌* @param token* @return*/public Claims parseAccessToken(String token) {return Jwts.parser().setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token).getBody();}/*** 生成刷新令牌refreshToken* @param userId* @param deviceId* @param tokenVersion* @return*/public String generateRefreshToken(Long userId, String deviceId, Integer tokenVersion) {String refreshToken = UUID.randomUUID().toString();AuthRefreshTokenInfo refreshTokenInfo = AuthRefreshTokenInfo.builder().userId(userId).deviceId(deviceId).tokenVersion(tokenVersion).build();String refreshKey = "refresh_token:" + refreshToken;redisTemplate.opsForValue().set(refreshKey, refreshTokenInfo, Duration.ofMillis(refreshExpirationMs));return refreshToken;}
}
2.3 請求攔截器
@Component
public class AuthInterceptor implements HandlerInterceptor {@Autowiredprivate AuthJwtUtil jwtUtil;@Autowiredprivate AuthService authService;@Overridepublic boolean preHandle(