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 b19f68a1..259a26a5 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 @@ -152,10 +152,8 @@ public class AuthServiceImpl implements AuthService { */ @Override public void logout() { - String token = SecurityUtils.getTokenFromRequest(); - if (StrUtil.isNotBlank(token) && token.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX )) { - token = token.substring(SecurityConstants.BEARER_TOKEN_PREFIX .length()); - // 将JWT令牌加入黑名单 + String token = SecurityUtils.getAccessToken(); + if (StrUtil.isNotBlank(token)) { tokenManager.invalidateToken(token); // 清除Security上下文 SecurityContextHolder.clearContext(); diff --git a/src/main/java/com/youlai/boot/security/token/JwtTokenManager.java b/src/main/java/com/youlai/boot/security/token/JwtTokenManager.java index bba10d7c..5f3f5397 100644 --- a/src/main/java/com/youlai/boot/security/token/JwtTokenManager.java +++ b/src/main/java/com/youlai/boot/security/token/JwtTokenManager.java @@ -132,49 +132,46 @@ public class JwtTokenManager implements TokenManager { * @return 是否有效 */ private boolean validateToken(String token, boolean validateRefreshToken) { - try { - JWT jwt = JWTUtil.parseToken(token); - // 检查 Token 是否有效(验签 + 是否过期) - boolean isValid = jwt.setKey(secretKey).validate(0); + JWT jwt = JWTUtil.parseToken(token); + // 检查 Token 是否有效(验签 + 是否过期) + boolean isValid = jwt.setKey(secretKey).validate(0); - if (isValid) { - JSONObject payloads = jwt.getPayloads(); - // 1. 校验刷新令牌类型(仅在校验刷新令牌场景启用) - String jti = payloads.getStr(JWTPayload.JWT_ID); - if (validateRefreshToken) { - //刷新token需要校验token类别 - boolean isRefreshToken = payloads.getBool(JwtClaimConstants.TOKEN_TYPE); - if (!isRefreshToken) { - return false; - } - } - // 2. 校验安全版本号(用于按用户维度失效历史 Token) - Long userId = payloads.getLong(JwtClaimConstants.USER_ID); - if (userId != null) { - // 老版本 Token 可能没有 SECURITY_VERSION 声明,视为 0 版本 - Integer tokenVersionRaw = payloads.getInt(JwtClaimConstants.SECURITY_VERSION); - int tokenVersion = tokenVersionRaw != null ? tokenVersionRaw : 0; - - String versionKey = StrUtil.format(RedisConstants.Auth.USER_SECURITY_VERSION, userId); - Integer currentVersionRaw = (Integer) redisTemplate.opsForValue().get(versionKey); - int currentVersion = currentVersionRaw != null ? currentVersionRaw : 0; - - // 如果当前版本号比 Token 携带的版本号新,则认为该 Token 已失效 - if (tokenVersion < currentVersion) { - return false; - } - } - - // 3. 判断是否在黑名单中,如果在,则返回 false 标识Token无效 - if (Boolean.TRUE.equals(redisTemplate.hasKey(StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, jti)))) { + if (isValid) { + JSONObject payloads = jwt.getPayloads(); + // 1. 校验刷新令牌类型(仅在校验刷新令牌场景启用) + String jti = payloads.getStr(JWTPayload.JWT_ID); + if (validateRefreshToken) { + //刷新token需要校验token类别 + boolean isRefreshToken = payloads.getBool(JwtClaimConstants.TOKEN_TYPE); + if (!isRefreshToken) { return false; } } - return isValid; - } catch (Exception gitignore) { - // token 验证 + // 2. 校验安全版本号(用于按用户维度失效历史 Token) + // 场景示例:用户修改密码、被管理员强制下线、手动“踢所有端”后,将用户安全版本号 +1,旧版本 Token 全部失效 + Long userId = payloads.getLong(JwtClaimConstants.USER_ID); + if (userId != null) { + // 老版本 Token 可能没有 SECURITY_VERSION 声明,视为 0 版本 + Integer tokenVersionRaw = payloads.getInt(JwtClaimConstants.SECURITY_VERSION); + int tokenVersion = tokenVersionRaw != null ? tokenVersionRaw : 0; + + String versionKey = StrUtil.format(RedisConstants.Auth.USER_SECURITY_VERSION, userId); + Integer currentVersionRaw = (Integer) redisTemplate.opsForValue().get(versionKey); + int currentVersion = currentVersionRaw != null ? currentVersionRaw : 0; + + // 如果当前版本号比 Token 携带的版本号新,则认为该 Token 已失效 + if (tokenVersion < currentVersion) { + return false; + } + } + + // 3. 判断是否在黑名单中,如果在,则返回 false 标识Token无效 + // 场景示例:单点退出登录、后台手动注销某个会话、封禁账号后立即阻断当前 Token 等 + if (Boolean.TRUE.equals(redisTemplate.hasKey(StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, jti)))) { + return false; + } } - return false; + return isValid; } /** @@ -210,7 +207,6 @@ public class JwtTokenManager implements TokenManager { // 永不过期的Token永久加入黑名单 redisTemplate.opsForValue().set(blacklistTokenKey, Boolean.TRUE); } - ; } /** diff --git a/src/main/java/com/youlai/boot/security/token/RedisTokenManager.java b/src/main/java/com/youlai/boot/security/token/RedisTokenManager.java index 7efefaae..9c899156 100644 --- a/src/main/java/com/youlai/boot/security/token/RedisTokenManager.java +++ b/src/main/java/com/youlai/boot/security/token/RedisTokenManager.java @@ -165,8 +165,8 @@ public class RedisTokenManager implements TokenManager { */ @Override public void invalidateToken(String token) { - OnlineUser onlineUser = (OnlineUser) redisTemplate.opsForValue().get(formatTokenKey(token)); - if (onlineUser != null) { + Object value = redisTemplate.opsForValue().get(formatTokenKey(token)); + if (value instanceof OnlineUser onlineUser) { Long userId = onlineUser.getUserId(); invalidateUserSessions(userId); } @@ -186,20 +186,18 @@ public class RedisTokenManager implements TokenManager { // 1. 删除访问令牌相关 String userAccessKey = StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, userId); Object accessTokenValue = redisTemplate.opsForValue().get(userAccessKey); - Optional.of(accessTokenValue) - .map(String.class::cast) - .ifPresent(accessToken -> redisTemplate.delete(formatTokenKey(accessToken))); + if (accessTokenValue instanceof String accessToken) { + redisTemplate.delete(formatTokenKey(accessToken)); + } // 无论是否存在访问令牌映射,都尝试删除 userAccessKey redisTemplate.delete(userAccessKey); // 2. 删除刷新令牌相关 String userRefreshKey = StrUtil.format(RedisConstants.Auth.USER_REFRESH_TOKEN, userId); Object refreshTokenValue = redisTemplate.opsForValue().get(userRefreshKey); - Optional.of(refreshTokenValue) - .map(String.class::cast) - .ifPresent(refreshToken -> - redisTemplate.delete(StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken)) - ); + if (refreshTokenValue instanceof String refreshToken) { + redisTemplate.delete(StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken)); + } // 同样清理 userRefreshKey 本身 redisTemplate.delete(userRefreshKey); } @@ -237,9 +235,9 @@ public class RedisTokenManager implements TokenManager { // 单设备登录控制,删除旧的访问令牌 if (!allowMultiLogin) { Object oldAccessTokenValue = redisTemplate.opsForValue().get(userAccessKey); - Optional.of(oldAccessTokenValue) - .map(String.class::cast) - .ifPresent(oldAccessToken -> redisTemplate.delete(formatTokenKey(oldAccessToken))); + if (oldAccessTokenValue instanceof String oldAccessToken) { + redisTemplate.delete(formatTokenKey(oldAccessToken)); + } } // 存储访问令牌映射(用户ID -> 访问令牌),用于单设备登录控制删除旧的访问令牌和刷新令牌时删除旧令牌 setRedisValue(userAccessKey, accessToken, securityProperties.getSession().getAccessTokenTimeToLive()); diff --git a/src/main/java/com/youlai/boot/security/util/SecurityUtils.java b/src/main/java/com/youlai/boot/security/util/SecurityUtils.java index 48372e53..94ce0434 100644 --- a/src/main/java/com/youlai/boot/security/util/SecurityUtils.java +++ b/src/main/java/com/youlai/boot/security/util/SecurityUtils.java @@ -113,7 +113,7 @@ public class SecurityUtils { * * @return Token 字符串 */ - public static String getTokenFromRequest() { + public static String getAccessToken() { ServletRequestAttributes servletRequestAttributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()); if(Objects.isNull(servletRequestAttributes)) { return null;