feat: 更新 JWT 黑名单服务,支持令牌过期时间管理并添加定时清理功能

This commit is contained in:
enoch 2025-06-06 08:16:47 +00:00
parent 400a06f5a3
commit 244f0e7667
6 changed files with 45 additions and 11 deletions

View File

@ -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) {

View File

@ -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);
}

View File

@ -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<String, Date> 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());
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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