双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(