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管理方案。这套方案的核心在于:

  1. 双Token机制:平衡安全性和用户体验
  2. 黑名单管理:实现Token的主动失效
  3. 网关统一认证:简化微服务认证逻辑
  4. 自动续期:提升用户体验
  5. 强制登出:保障账户安全

这套方案已经在多个生产项目中得到验证,能够有效解决JWT的生命周期管理问题。在实际应用中,还需要根据具体的业务场景进行调整和优化。

如果你觉得这篇文章对你有帮助,欢迎关注我们的公众号"服务端技术精选",获取更多实用的技术干货!


标题:Spring Cloud Gateway + JWT 刷新 + 黑名单:Token 自动续期与强制登出全方案
作者:jiangyi
地址:http://jiangyi.space/articles/2026/02/07/1770271353951.html

    0 评论
avatar