From 2af4581f2d416f865e2777fd36b87f682c86e6e3 Mon Sep 17 00:00:00 2001 From: Theo <971366405@qq.com> Date: Fri, 13 Jun 2025 17:15:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E6=94=B9=E8=BF=9B=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E4=BB=A4=E7=89=8C=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 JWT 中添加 tokenType 字段,用于区分访问令牌和刷新令牌 - 重新实现刷新令牌验证逻辑,增加 tokenType 校验 - 优化 refreshToken 方法,直接使用 validateRefreshToken进行验证 - 移除不必要的代码,提高代码可读性和维护性 --- .../auth/service/impl/AuthServiceImpl.java | 25 ++--- .../common/constant/JwtClaimConstants.java | 5 + .../core/security/token/JwtTokenManager.java | 92 +++++++++++++------ 3 files changed, 78 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/youlai/boot/auth/service/impl/AuthServiceImpl.java b/src/main/java/com/youlai/boot/auth/service/impl/AuthServiceImpl.java index a1bd7740..2e844a6d 100644 --- a/src/main/java/com/youlai/boot/auth/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/youlai/boot/auth/service/impl/AuthServiceImpl.java @@ -7,18 +7,18 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import com.youlai.boot.auth.enums.CaptchaTypeEnum; 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.service.AuthService; import com.youlai.boot.common.constant.RedisConstants; 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.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.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.util.SecurityUtils; import com.youlai.boot.shared.sms.enums.SmsTypeEnum; import com.youlai.boot.shared.sms.service.SmsService; import lombok.RequiredArgsConstructor; @@ -29,8 +29,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; 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.util.HashMap; @@ -220,13 +218,6 @@ public class AuthServiceImpl implements AuthService { */ @Override public AuthenticationToken refreshToken(String refreshToken) { - // 验证刷新令牌 - boolean isValidate = tokenManager.validateRefreshToken(refreshToken); - - if (!isValidate) { - throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID); - } - // 刷新令牌有效,生成新的访问令牌 return tokenManager.refreshToken(refreshToken); } @@ -265,14 +256,14 @@ public class AuthServiceImpl implements AuthService { loginDTO.getEncryptedData(), loginDTO.getIv() ); - + // 执行认证 Authentication authentication = authenticationManager.authenticate(authenticationToken); - + // 认证成功后生成JWT令牌,并存入Security上下文 AuthenticationToken token = tokenManager.generateToken(authentication); SecurityContextHolder.getContext().setAuthentication(authentication); - + return token; } diff --git a/src/main/java/com/youlai/boot/common/constant/JwtClaimConstants.java b/src/main/java/com/youlai/boot/common/constant/JwtClaimConstants.java index a74ac0a3..8f6e2d4f 100644 --- a/src/main/java/com/youlai/boot/common/constant/JwtClaimConstants.java +++ b/src/main/java/com/youlai/boot/common/constant/JwtClaimConstants.java @@ -10,6 +10,11 @@ package com.youlai.boot.common.constant; */ public interface JwtClaimConstants { + /** + * 令牌类型 + */ + String TOKEN_TYPE = "tokenType"; + /** * 用户ID */ diff --git a/src/main/java/com/youlai/boot/core/security/token/JwtTokenManager.java b/src/main/java/com/youlai/boot/core/security/token/JwtTokenManager.java index 9df71182..d1470211 100644 --- a/src/main/java/com/youlai/boot/core/security/token/JwtTokenManager.java +++ b/src/main/java/com/youlai/boot/core/security/token/JwtTokenManager.java @@ -14,8 +14,8 @@ 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.SecurityProperties; -import com.youlai.boot.core.security.model.SysUserDetails; 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.data.redis.core.RedisTemplate; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -65,7 +65,7 @@ public class JwtTokenManager implements TokenManager { int refreshTokenTimeToLive = securityProperties.getSession().getRefreshTokenTimeToLive(); String accessToken = generateToken(authentication, accessTokenTimeToLive); - String refreshToken = generateToken(authentication, refreshTokenTimeToLive); + String refreshToken = generateToken(authentication, refreshTokenTimeToLive, true); return AuthenticationToken.builder() .accessToken(accessToken) @@ -109,26 +109,54 @@ public class JwtTokenManager implements TokenManager { */ @Override public boolean validateToken(String token) { - JWT jwt = JWTUtil.parseToken(token); - // 检查 Token 是否有效(验签 + 是否过期) - boolean isValid = jwt.setKey(secretKey).validate(0); - - if (isValid) { - // 检查 Token 是否已被加入黑名单(注销、修改密码等场景) - JSONObject payloads = jwt.getPayloads(); - String jti = payloads.getStr(JWTPayload.JWT_ID); - - // 判断是否在黑名单中,如果在,则返回 false 标识Token无效 - if (Boolean.TRUE.equals(redisTemplate.hasKey(StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, jti)))) { - return false; - } - } - return isValid; + return validateToken(token,false); } + /** + * 校验刷新令牌 + * + * @param refreshToken JWT Token + * @return 验证结果 + */ @Override public boolean validateRefreshToken(String refreshToken) { - return this.validateToken(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); + // 检查 Token 是否有效(验签 + 是否过期) + boolean isValid = jwt.setKey(secretKey).validate(0); + + if (isValid) { + // 检查 Token 是否已被加入黑名单(注销、修改密码等场景) + JSONObject payloads = jwt.getPayloads(); + String jti = payloads.getStr(JWTPayload.JWT_ID); + if(validateRefreshToken) { + //刷新token需要校验token类别 + boolean isRefreshToken = payloads.getBool(JwtClaimConstants.TOKEN_TYPE); + if (!isRefreshToken) { + return false; + } + } + // 判断是否在黑名单中,如果在,则返回 false 标识Token无效 + if (Boolean.TRUE.equals(redisTemplate.hasKey(StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, jti)))) { + return false; + } + } + return isValid; + } catch (Exception gitignore) { + // token 验证 + } + return false; } /** @@ -141,12 +169,9 @@ public class JwtTokenManager implements TokenManager { if (token.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX)) { token = token.substring(SecurityConstants.BEARER_TOKEN_PREFIX.length()); } - JWT jwt = JWTUtil.parseToken(token); JSONObject payloads = jwt.getPayloads(); - Integer expirationAt = payloads.getInt(JWTPayload.EXPIRES_AT); - // 黑名单Token Key String blacklistTokenKey = StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, payloads.getStr(JWTPayload.JWT_ID)); @@ -174,16 +199,13 @@ public class JwtTokenManager implements TokenManager { */ @Override public AuthenticationToken refreshToken(String refreshToken) { - - boolean isValid = validateToken(refreshToken); + boolean isValid = validateRefreshToken(refreshToken); if (!isValid) { throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID); } - Authentication authentication = parseToken(refreshToken); int accessTokenExpiration = securityProperties.getSession().getAccessTokenTimeToLive(); String newAccessToken = generateToken(authentication, accessTokenExpiration); - return AuthenticationToken.builder() .accessToken(newAccessToken) .refreshToken(refreshToken) @@ -196,13 +218,24 @@ public class JwtTokenManager implements TokenManager { * 生成 JWT Token * * @param authentication 认证信息 - * @param ttl 过期时间 + * @param ttl 过期时间 * @return JWT Token */ 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(); - Map payload = new HashMap<>(); payload.put(JwtClaimConstants.USER_ID, userDetails.getUserId()); // 用户ID payload.put(JwtClaimConstants.DEPT_ID, userDetails.getDeptId()); // 部门ID @@ -216,6 +249,10 @@ public class JwtTokenManager implements TokenManager { Date now = new Date(); payload.put(JWTPayload.ISSUED_AT, now); + payload.put(JwtClaimConstants.TOKEN_TYPE, false); + if (isRefreshToken) { + payload.put(JwtClaimConstants.TOKEN_TYPE, true); + } // 设置过期时间 -1 表示永不过期 if (ttl != -1) { @@ -227,4 +264,5 @@ public class JwtTokenManager implements TokenManager { return JWTUtil.createToken(payload, secretKey); } + }