feat(auth): 改进刷新令牌机制

- 在 JWT 中添加 tokenType 字段,用于区分访问令牌和刷新令牌
- 重新实现刷新令牌验证逻辑,增加 tokenType 校验
- 优化 refreshToken 方法,直接使用 validateRefreshToken进行验证
- 移除不必要的代码,提高代码可读性和维护性
This commit is contained in:
Theo
2025-06-13 17:15:31 +08:00
parent 7ecf34cf43
commit 2af4581f2d
3 changed files with 78 additions and 44 deletions

View File

@@ -7,18 +7,18 @@ import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.youlai.boot.auth.enums.CaptchaTypeEnum; import com.youlai.boot.auth.enums.CaptchaTypeEnum;
import com.youlai.boot.auth.model.CaptchaInfo; import com.youlai.boot.auth.model.CaptchaInfo;
import com.youlai.boot.auth.model.dto.WxMiniAppCodeLoginDTO;
import com.youlai.boot.auth.model.dto.WxMiniAppPhoneLoginDTO; import com.youlai.boot.auth.model.dto.WxMiniAppPhoneLoginDTO;
import com.youlai.boot.auth.service.AuthService;
import com.youlai.boot.common.constant.RedisConstants; import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.constant.SecurityConstants; import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.config.property.CaptchaProperties; import com.youlai.boot.config.property.CaptchaProperties;
import com.youlai.boot.core.security.extension.sms.SmsAuthenticationToken; import com.youlai.boot.core.security.extension.sms.SmsAuthenticationToken;
import com.youlai.boot.core.security.util.SecurityUtils; import com.youlai.boot.core.security.extension.wx.WxMiniAppCodeAuthenticationToken;
import com.youlai.boot.core.security.extension.wx.WxMiniAppPhoneAuthenticationToken;
import com.youlai.boot.core.security.model.AuthenticationToken; import com.youlai.boot.core.security.model.AuthenticationToken;
import com.youlai.boot.auth.model.dto.WxMiniAppCodeLoginDTO;
import com.youlai.boot.auth.service.AuthService;
import com.youlai.boot.core.security.token.TokenManager; import com.youlai.boot.core.security.token.TokenManager;
import com.youlai.boot.core.security.util.SecurityUtils;
import com.youlai.boot.shared.sms.enums.SmsTypeEnum; import com.youlai.boot.shared.sms.enums.SmsTypeEnum;
import com.youlai.boot.shared.sms.service.SmsService; import com.youlai.boot.shared.sms.service.SmsService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -29,8 +29,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.youlai.boot.core.security.extension.wx.WxMiniAppCodeAuthenticationToken;
import com.youlai.boot.core.security.extension.wx.WxMiniAppPhoneAuthenticationToken;
import java.awt.*; import java.awt.*;
import java.util.HashMap; import java.util.HashMap;
@@ -220,13 +218,6 @@ public class AuthServiceImpl implements AuthService {
*/ */
@Override @Override
public AuthenticationToken refreshToken(String refreshToken) { public AuthenticationToken refreshToken(String refreshToken) {
// 验证刷新令牌
boolean isValidate = tokenManager.validateRefreshToken(refreshToken);
if (!isValidate) {
throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID);
}
// 刷新令牌有效,生成新的访问令牌
return tokenManager.refreshToken(refreshToken); return tokenManager.refreshToken(refreshToken);
} }

View File

@@ -10,6 +10,11 @@ package com.youlai.boot.common.constant;
*/ */
public interface JwtClaimConstants { public interface JwtClaimConstants {
/**
* 令牌类型
*/
String TOKEN_TYPE = "tokenType";
/** /**
* 用户ID * 用户ID
*/ */

View File

@@ -14,8 +14,8 @@ import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.result.ResultCode; import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.config.property.SecurityProperties; import com.youlai.boot.config.property.SecurityProperties;
import com.youlai.boot.core.security.model.SysUserDetails;
import com.youlai.boot.core.security.model.AuthenticationToken; import com.youlai.boot.core.security.model.AuthenticationToken;
import com.youlai.boot.core.security.model.SysUserDetails;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -65,7 +65,7 @@ public class JwtTokenManager implements TokenManager {
int refreshTokenTimeToLive = securityProperties.getSession().getRefreshTokenTimeToLive(); int refreshTokenTimeToLive = securityProperties.getSession().getRefreshTokenTimeToLive();
String accessToken = generateToken(authentication, accessTokenTimeToLive); String accessToken = generateToken(authentication, accessTokenTimeToLive);
String refreshToken = generateToken(authentication, refreshTokenTimeToLive); String refreshToken = generateToken(authentication, refreshTokenTimeToLive, true);
return AuthenticationToken.builder() return AuthenticationToken.builder()
.accessToken(accessToken) .accessToken(accessToken)
@@ -109,6 +109,29 @@ public class JwtTokenManager implements TokenManager {
*/ */
@Override @Override
public boolean validateToken(String token) { public boolean validateToken(String token) {
return validateToken(token,false);
}
/**
* 校验刷新令牌
*
* @param refreshToken JWT Token
* @return 验证结果
*/
@Override
public boolean validateRefreshToken(String refreshToken) {
return validateToken(refreshToken,true);
}
/**
* 校验令牌
*
* @param token JWT Token
* @param validateRefreshToken 是否校验刷新令牌
* @return 是否有效
*/
private boolean validateToken(String token, boolean validateRefreshToken) {
try {
JWT jwt = JWTUtil.parseToken(token); JWT jwt = JWTUtil.parseToken(token);
// 检查 Token 是否有效(验签 + 是否过期) // 检查 Token 是否有效(验签 + 是否过期)
boolean isValid = jwt.setKey(secretKey).validate(0); boolean isValid = jwt.setKey(secretKey).validate(0);
@@ -117,18 +140,23 @@ public class JwtTokenManager implements TokenManager {
// 检查 Token 是否已被加入黑名单(注销、修改密码等场景) // 检查 Token 是否已被加入黑名单(注销、修改密码等场景)
JSONObject payloads = jwt.getPayloads(); JSONObject payloads = jwt.getPayloads();
String jti = payloads.getStr(JWTPayload.JWT_ID); String jti = payloads.getStr(JWTPayload.JWT_ID);
if(validateRefreshToken) {
//刷新token需要校验token类别
boolean isRefreshToken = payloads.getBool(JwtClaimConstants.TOKEN_TYPE);
if (!isRefreshToken) {
return false;
}
}
// 判断是否在黑名单中,如果在,则返回 false 标识Token无效 // 判断是否在黑名单中,如果在,则返回 false 标识Token无效
if (Boolean.TRUE.equals(redisTemplate.hasKey(StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, jti)))) { if (Boolean.TRUE.equals(redisTemplate.hasKey(StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, jti)))) {
return false; return false;
} }
} }
return isValid; return isValid;
} catch (Exception gitignore) {
// token 验证
} }
return false;
@Override
public boolean validateRefreshToken(String refreshToken) {
return this.validateToken(refreshToken);
} }
/** /**
@@ -141,12 +169,9 @@ public class JwtTokenManager implements TokenManager {
if (token.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX)) { if (token.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX)) {
token = token.substring(SecurityConstants.BEARER_TOKEN_PREFIX.length()); token = token.substring(SecurityConstants.BEARER_TOKEN_PREFIX.length());
} }
JWT jwt = JWTUtil.parseToken(token); JWT jwt = JWTUtil.parseToken(token);
JSONObject payloads = jwt.getPayloads(); JSONObject payloads = jwt.getPayloads();
Integer expirationAt = payloads.getInt(JWTPayload.EXPIRES_AT); Integer expirationAt = payloads.getInt(JWTPayload.EXPIRES_AT);
// 黑名单Token Key // 黑名单Token Key
String blacklistTokenKey = StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, payloads.getStr(JWTPayload.JWT_ID)); String blacklistTokenKey = StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, payloads.getStr(JWTPayload.JWT_ID));
@@ -174,16 +199,13 @@ public class JwtTokenManager implements TokenManager {
*/ */
@Override @Override
public AuthenticationToken refreshToken(String refreshToken) { public AuthenticationToken refreshToken(String refreshToken) {
boolean isValid = validateRefreshToken(refreshToken);
boolean isValid = validateToken(refreshToken);
if (!isValid) { if (!isValid) {
throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID); throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID);
} }
Authentication authentication = parseToken(refreshToken); Authentication authentication = parseToken(refreshToken);
int accessTokenExpiration = securityProperties.getSession().getAccessTokenTimeToLive(); int accessTokenExpiration = securityProperties.getSession().getAccessTokenTimeToLive();
String newAccessToken = generateToken(authentication, accessTokenExpiration); String newAccessToken = generateToken(authentication, accessTokenExpiration);
return AuthenticationToken.builder() return AuthenticationToken.builder()
.accessToken(newAccessToken) .accessToken(newAccessToken)
.refreshToken(refreshToken) .refreshToken(refreshToken)
@@ -200,9 +222,20 @@ public class JwtTokenManager implements TokenManager {
* @return JWT Token * @return JWT Token
*/ */
private String generateToken(Authentication authentication, int ttl) { private String generateToken(Authentication authentication, int ttl) {
return generateToken(authentication, ttl, false);
}
/**
* 生成 JWT Token
*
* @param authentication 认证信息
* @param ttl 过期时间
* @param isRefreshToken 类型是否为刷新token
* @return JWT Token
*/
private String generateToken(Authentication authentication, int ttl, boolean isRefreshToken) {
SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal(); SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
Map<String, Object> payload = new HashMap<>(); Map<String, Object> payload = new HashMap<>();
payload.put(JwtClaimConstants.USER_ID, userDetails.getUserId()); // 用户ID payload.put(JwtClaimConstants.USER_ID, userDetails.getUserId()); // 用户ID
payload.put(JwtClaimConstants.DEPT_ID, userDetails.getDeptId()); // 部门ID payload.put(JwtClaimConstants.DEPT_ID, userDetails.getDeptId()); // 部门ID
@@ -216,6 +249,10 @@ public class JwtTokenManager implements TokenManager {
Date now = new Date(); Date now = new Date();
payload.put(JWTPayload.ISSUED_AT, now); payload.put(JWTPayload.ISSUED_AT, now);
payload.put(JwtClaimConstants.TOKEN_TYPE, false);
if (isRefreshToken) {
payload.put(JwtClaimConstants.TOKEN_TYPE, true);
}
// 设置过期时间 -1 表示永不过期 // 设置过期时间 -1 表示永不过期
if (ttl != -1) { if (ttl != -1) {
@@ -227,4 +264,5 @@ public class JwtTokenManager implements TokenManager {
return JWTUtil.createToken(payload, secretKey); return JWTUtil.createToken(payload, secretKey);
} }
} }