diff --git a/src/main/java/com/userauth/restuserauth/RestUserAuthApplication.java b/src/main/java/com/userauth/restuserauth/RestUserAuthApplication.java index ab6dd84..14cd76f 100644 --- a/src/main/java/com/userauth/restuserauth/RestUserAuthApplication.java +++ b/src/main/java/com/userauth/restuserauth/RestUserAuthApplication.java @@ -2,8 +2,10 @@ package com.userauth.restuserauth; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class RestUserAuthApplication { public static void main(String[] args) { diff --git a/src/main/java/com/userauth/restuserauth/controller/AuthController.java b/src/main/java/com/userauth/restuserauth/controller/AuthController.java index 2bcb32b..9c691ff 100644 --- a/src/main/java/com/userauth/restuserauth/controller/AuthController.java +++ b/src/main/java/com/userauth/restuserauth/controller/AuthController.java @@ -1,5 +1,6 @@ package com.userauth.restuserauth.controller; +import java.util.Date; import java.util.Map; import org.slf4j.Logger; @@ -85,8 +86,10 @@ public class AuthController { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { String jwt = bearerToken.substring(7); - // 将当前 JWT 加入黑名单 - jwtBlacklistService.blacklistToken(jwt); + // 获取令牌的过期时间 + Date expirationDate = tokenProvider.getExpirationDateFromToken(jwt); + // 将当前JWT及其过期时间加入黑名单 + jwtBlacklistService.blacklistToken(jwt, expirationDate); logger.info("令牌已加入黑名单:{}", jwt); } diff --git a/src/main/java/com/userauth/restuserauth/security/JwtBlacklistService.java b/src/main/java/com/userauth/restuserauth/security/JwtBlacklistService.java index f1d28d0..8405d26 100644 --- a/src/main/java/com/userauth/restuserauth/security/JwtBlacklistService.java +++ b/src/main/java/com/userauth/restuserauth/security/JwtBlacklistService.java @@ -4,23 +4,28 @@ import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service public class JwtBlacklistService { + private static final Logger logger = LoggerFactory.getLogger(JwtBlacklistService.class); + // 使用 ConcurrentHashMap 作为内存中的黑名单存储。 - // 在生产环境中,建议使用 Redis 等分布式缓存来支持多实例部署。 private final Map blacklist = new ConcurrentHashMap<>(); /** - * 将令牌加入黑名单。 + * 将令牌及其过期时间加入黑名单。 * @param token 要加入黑名单的 JWT。 + * @param expirationDate 令牌的过期时间。 */ - public void blacklistToken(String token) { - // 为了简化,我们暂时不处理过期清理,直接加入。 - // 在实际应用中,可以存储令牌的过期时间,并定期清理已过期的令牌以防止内存泄漏。 - blacklist.put(token, new Date()); + public void blacklistToken(String token, Date expirationDate) { + if (token != null && expirationDate != null) { + blacklist.put(token, expirationDate); + } } /** @@ -31,4 +36,18 @@ public class JwtBlacklistService { public boolean isBlacklisted(String token) { return blacklist.containsKey(token); } + + /** + * 定时任务,定期清理黑名单中已过期的令牌。 + * cron 表达式 "0 0 * * * *" 表示每小时的 0 分 0 秒执行一次。 + */ + @Scheduled(cron = "*/30 * * * * *") + public void cleanupExpiredTokens() { + logger.info("开始清理黑名单中的过期令牌... 当前黑名单大小:{}", blacklist.size()); + + // 使用 removeIf 安全地从 ConcurrentHashMap 中移除过期的条目 + blacklist.entrySet().removeIf(entry -> entry.getValue().before(new Date())); + + logger.info("清理完成。当前黑名单大小:{}", blacklist.size()); + } } diff --git a/src/main/java/com/userauth/restuserauth/security/JwtTokenProvider.java b/src/main/java/com/userauth/restuserauth/security/JwtTokenProvider.java index bed0201..19425f6 100644 --- a/src/main/java/com/userauth/restuserauth/security/JwtTokenProvider.java +++ b/src/main/java/com/userauth/restuserauth/security/JwtTokenProvider.java @@ -61,4 +61,14 @@ public class JwtTokenProvider { } return false; } + + // 新增:从 JWT 中获取过期时间 + public Date getExpirationDateFromToken(String token) { + Claims claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + return claims.getExpiration(); + } } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index afcc1f8..87d3269 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -27,4 +27,4 @@ spring.jpa.properties.hibernate.format_sql=true # 用于签发 JWT 的密钥。请务必修改为一个足够长且复杂的字符串,不要在生产环境中泄露 app.jwt.secret=YourSuperSecretKeyForJWTsWhichIsLongAndSecureAbcdAbcdAbcdAbcdAbcdAbcdAbcdAbcdAbcdAbcd # JWT 的过期时间(毫秒),这里设置为 1 小时 -app.jwt.expiration-ms=3600000 +app.jwt.expiration-ms=60000 diff --git a/src/test/resources/rest-test/auth.http b/src/test/resources/rest-test/auth.http index cd00fa7..203c63e 100644 --- a/src/test/resources/rest-test/auth.http +++ b/src/test/resources/rest-test/auth.http @@ -20,9 +20,9 @@ Content-Type: application/json ### Get user info GET http://localhost:8080/api/user/me -Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0dXNlcjAyIiwiaWF0IjoxNzQ5MTk2ODQxLCJleHAiOjE3NDkyMDA0NDF9.BM9ZeCP8Xbesk8hQj04Rr4EMRQ84fpjX9ikk8yIUPvF0mS5pVoR5J_bEJ1It5C6UkFteq0v8VVK9nWHDMfIEGg +Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0dXNlcjAyIiwiaWF0IjoxNzQ5MTk3NzEzLCJleHAiOjE3NDkxOTc3NzN9.34iPSN77CJ2yrOp9pxCBguNkEJqu8VTgrIb7oWPQh1yTQQVcHMxQuMlMolorUOT216BF0_7b9vWS_COONQzGQA ### Logout POST http://localhost:8080/api/auth/logout -Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0dXNlcjAyIiwiaWF0IjoxNzQ5MTk2ODQxLCJleHAiOjE3NDkyMDA0NDF9.BM9ZeCP8Xbesk8hQj04Rr4EMRQ84fpjX9ikk8yIUPvF0mS5pVoR5J_bEJ1It5C6UkFteq0v8VVK9nWHDMfIEGg +Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0dXNlcjAyIiwiaWF0IjoxNzQ5MTk3NzEzLCJleHAiOjE3NDkxOTc3NzN9.34iPSN77CJ2yrOp9pxCBguNkEJqu8VTgrIb7oWPQh1yTQQVcHMxQuMlMolorUOT216BF0_7b9vWS_COONQzGQA