xkcoding/spring-boot-demo · error · SecurityException

5002

5002

Error message

token 已过期,请重新登录!

What it means

parseJWT checks the Redis TTL of key Consts.REDIS_JWT_KEY_PREFIX+username; if stringRedisTemplate.getExpire returns null or <= 0 it throws SecurityException(Status.TOKEN_EXPIRED) (README:226; impl JwtUtil.java:97-98). This is the REDIS-side expiry, separate from the JWT exp claim - the token may still be cryptographically valid but is treated as expired because Redis no longer holds it.

Source

Thrown at demo-rbac-security/README.md:226

     * 解析JWT
     *
     * @param jwt JWT
     * @return {@link Claims}
     */
    public Claims parseJWT(String jwt) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(jwtConfig.getKey())
                    .parseClaimsJws(jwt)
                    .getBody();

            String username = claims.getSubject();
            String redisKey = Consts.REDIS_JWT_KEY_PREFIX + username;

            // 校验redis中的JWT是否存在
            Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS);
            if (Objects.isNull(expire) || expire <= 0) {
                throw new SecurityException(Status.TOKEN_EXPIRED);
            }

            // 校验redis中的JWT是否与当前的一致,不一致则代表用户已注销/用户在不同设备登录,均代表JWT已过期
            String redisToken = stringRedisTemplate.opsForValue()
                    .get(redisKey);
            if (!StrUtil.equals(jwt, redisToken)) {
                throw new SecurityException(Status.TOKEN_OUT_OF_CTRL);
            }
            return claims;
        } catch (ExpiredJwtException e) {
            log.error("Token 已过期");
            throw new SecurityException(Status.TOKEN_EXPIRED);
        } catch (UnsupportedJwtException e) {
            log.error("不支持的 Token");
            throw new SecurityException(Status.TOKEN_PARSE_ERROR);
        } catch (MalformedJwtException e) {
            log.error("Token 无效");
            throw new SecurityException(Status.TOKEN_PARSE_ERROR);

View on GitHub (pinned to 87a142f960)

Solutions

  1. Enable Redis persistence (AOF/RDB) if tokens must survive restarts
  2. Prompt re-login to repopulate the Redis JWT key via createJWT
  3. Keep the Redis ttl aligned with the JWT exp ttl set in createJWT
  4. Avoid flushing the JWT keyspace in shared environments
Defensive patterns

Strategy: validation

Validate before calling

Long expire = stringRedisTemplate.getExpire(Consts.REDIS_JWT_KEY_PREFIX + username, TimeUnit.MILLISECONDS);
if (expire == null || expire <= 0) {
    // Redis no longer holds the token - force re-login before trusting the JWT
}

Try / catch

try { jwtUtil.parseJWT(token); }
catch (SecurityException e) { if (Status.TOKEN_EXPIRED.getCode().equals(e.getCode())) { /* Redis TTL expired - re-login */ } }

Prevention

When it happens

Trigger: Redis evicted or expired the JWT key (its ttl elapsed) before the JWT exp claim expired; Redis was flushed or restarted without persistence; invalidateJWT deleted the key on logout.

Common situations: Redis restart without AOF/RDB; a manual FLUSHDB; the Redis ttl set shorter than the JWT exp ttl; logout performed on another device.

Related errors


AI-assisted analysis of xkcoding/spring-boot-demo@87a142f960 (2026-08-14). Data as JSON: /api/errors/1e2d3c959bb44b61. Report an issue: GitHub.