Spring Cloud Gateway + JWT 刷新 + 黑名单:Token 自动续期与强制登出全方案
前言
在微服务架构中,身份认证和授权是一个核心问题。JWT(JSON Web Token)因其无状态、跨域友好等特性,成为了最流行的认证方案之一。但JWT也有一个致命弱点——一旦签发就无法主动失效,这给安全管控带来了巨大挑战。
想象一下这样的场景:用户A登录后获得了JWT,但随后账号被盗用,或者用户B离职后仍持有有效的JWT。在传统JWT方案中,这些Token会一直有效直到自然过期,形成了严重的安全隐患。
今天,我就来和大家分享一套完整的解决方案——基于Spring Cloud Gateway的JWT自动续期与强制登出全方案,彻底解决JWT的生命周期管理难题。
传统JWT方案的痛点
在深入解决方案之前,我们先来梳理一下传统JWT方案面临的核心问题:
1. 无法主动失效
JWT是无状态的,一旦签发就无法在服务端主动使其失效。即使用户修改了密码或账号被禁用,原有的JWT仍然有效,直到自然过期。
2. 长期Token风险
如果JWT过期时间设置得太长,安全风险增加;设置得太短,用户体验变差,频繁重新登录。
3. 登出困难
用户登出时,只能在客户端清除Token,但服务端无法感知,Token依然有效。
4. 并发登录问题
无法有效管理同一账户的多设备登录,难以实现强制下线等功能。
我们的解决方案
经过深入研究和实践,我们提出了一套基于双Token机制的完整解决方案:
1. 双Token设计
我们采用Access Token + Refresh Token的双Token机制:
- Access Token:短期有效(如15分钟),用于日常接口访问
- Refresh Token:长期有效(如7天),用于获取新的Access Token
这种设计既保证了安全性(Access Token有效期短),又兼顾了用户体验(通过Refresh Token避免频繁登录)。
2. 黑名单机制
为了解决JWT无法主动失效的问题,我们引入了黑名单机制:
- 使用Redis存储已失效的Token
- 每次请求都会检查Token是否在黑名单中
- 实现真正的强制登出功能
3. 网关统一认证
在Spring Cloud Gateway层统一进行JWT验证和管理:
- 拦截所有需要认证的请求
- 统一处理认证逻辑
- 将用户信息传递给下游服务
Spring Cloud Gateway实战实现
下面,我通过实际代码来展示这套方案的实现。
1. JWT工具类
首先,我们需要一个完整的JWT工具类来处理Token的生成和验证:
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.access-token-expiration}")
private Long accessTokenExpiration;
@Value("${jwt.refresh-token-expiration}")
private Long refreshTokenExpiration;
/**
* 生成访问令牌
*/
public String generateAccessToken(String username, String userId, String role) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("role", role);
return createToken(claims, username, accessTokenExpiration);
}
/**
* 生成刷新令牌
*/
public String generateRefreshToken(String username, String userId) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("type", "refresh");
return createToken(claims, username, refreshTokenExpiration);
}
// 更多实现...
}
2. 黑名单服务
使用Redis实现Token黑名单管理:
@Service
public class TokenBlacklistService {
@Autowired
private ReactiveRedisTemplate<String, String> reactiveRedisTemplate;
private static final String BLACKLIST_PREFIX = "jwt_blacklist:";
/**
* 将Token加入黑名单
*/
public Mono<Boolean> addTokenToBlacklist(String token, Date expiration) {
String key = BLACKLIST_PREFIX + token;
long ttl = Math.max(0, (expiration.getTime() - System.currentTimeMillis()) / 1000);
Duration duration = Duration.ofSeconds(ttl);
return reactiveRedisTemplate.opsForValue().set(key, "blacklisted", duration);
}
/**
* 检查Token是否在黑名单中
*/
public Mono<Boolean> isTokenBlacklisted(String token) {
String key = BLACKLIST_PREFIX + token;
return reactiveRedisTemplate.hasKey(key);
}
}
3. 网关认证过滤器
在网关层实现统一的认证逻辑:
@Component
public class JwtAuthenticationFilter implements GlobalFilter, Ordered {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private TokenBlacklistService tokenBlacklistService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 从请求头中获取JWT
String jwt = getJwtFromRequest(request);
if (jwt == null || jwt.isEmpty()) {
return unauthorizedResponse(exchange, "Missing JWT token");
}
// 检查Token是否在黑名单中
return tokenBlacklistService.isTokenBlacklisted(jwt)
.flatMap(isBlacklisted -> {
if (Boolean.TRUE.equals(isBlacklisted)) {
return unauthorizedResponse(exchange, "Token is blacklisted");
}
// 验证JWT并提取用户信息
try {
Claims claims = jwtUtil.getAllClaimsFromToken(jwt);
// 检查Token是否即将过期,自动续期
long timeUntilExpiration = claims.getExpiration().getTime() - System.currentTimeMillis();
if (timeUntilExpiration < 5 * 60 * 1000L) { // 5分钟内过期
// 触发自动续期逻辑
}
// 将用户信息添加到请求头中传递给下游服务
String userId = jwtUtil.getUserIdFromToken(jwt);
String username = claims.getSubject();
String role = jwtUtil.getRoleFromToken(jwt);
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-User-ID", userId)
.header("X-Username", username)
.header("X-Role", role)
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());
} catch (Exception e) {
return unauthorizedResponse(exchange, "Invalid JWT token");
}
});
}
}
自动续期与强制登出实现
1. 自动续期机制
自动续期是提升用户体验的关键。当检测到Access Token即将过期时,系统会自动使用Refresh Token获取新的Access Token:
// 前端检测Token过期时间,提前请求刷新
if (timeUntilExpiration < 5 * 60 * 1000L) { // 5分钟内过期
// 调用刷新接口
refreshToken();
}
2. 强制登出实现
强制登出通过将Token加入黑名单实现:
@PostMapping("/logout")
public Mono<ResponseEntity<String>> logout(@RequestHeader("Authorization") String token) {
String jwt = token.replace("Bearer ", "");
Date expiration = jwtUtil.getExpirationDateFromToken(jwt);
// 将Token加入黑名单
return tokenBlacklistService.addTokenToBlacklist(jwt, expiration)
.map(success -> ResponseEntity.ok("Logged out successfully"))
.switchIfEmpty(Mono.just(ResponseEntity.status(500).body("Logout failed")));
}
安全加固与最佳实践
1. 密钥安全管理
- 使用强密钥,定期更换
- 在生产环境中通过环境变量或配置中心管理密钥
- 考虑使用JWK(JSON Web Key)进行密钥轮换
2. Token绑定
将Token与特定信息绑定,如IP地址、User-Agent等,防止Token盗用:
// 在Token中包含设备信息
claims.put("deviceInfo", deviceInfo);
3. 频率限制
对Token刷新接口实施频率限制,防止恶意刷取Token。
4. HTTPS强制
所有涉及Token的通信必须使用HTTPS,防止中间人攻击。
性能优化
1. 缓存策略
- 在网关层缓存已验证的Token,减少重复验证开销
- 合理设置Redis连接池参数
2. 异步处理
充分利用Spring WebFlux的异步特性,提高并发处理能力。
3. 黑名单清理
定期清理过期的黑名单记录,避免Redis内存无限增长。
总结
通过今天的分享,我们了解了如何构建一套完整的JWT管理方案。这套方案的核心在于:
- 双Token机制:平衡安全性和用户体验
- 黑名单管理:实现Token的主动失效
- 网关统一认证:简化微服务认证逻辑
- 自动续期:提升用户体验
- 强制登出:保障账户安全
这套方案已经在多个生产项目中得到验证,能够有效解决JWT的生命周期管理问题。在实际应用中,还需要根据具体的业务场景进行调整和优化。
如果你觉得这篇文章对你有帮助,欢迎关注我们的公众号"服务端技术精选",获取更多实用的技术干货!
标题:Spring Cloud Gateway + JWT 刷新 + 黑名单:Token 自动续期与强制登出全方案
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/02/07/1770271353951.html