{"record":{"id":"1a214874a7f1b606","repo":"xkcoding/spring-boot-demo","slug":"5002","errorCode":"5002","errorMessage":"token 已过期，请重新登录！","messagePattern":"token 已过期，请重新登录！","errorType":"exception","errorClass":"SecurityException","httpStatus":null,"severity":"warning","filePath":"demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java","lineNumber":98,"sourceCode":"    }\n\n    /**\n     * 解析JWT\n     *\n     * @param jwt JWT\n     * @return {@link Claims}\n     */\n    public Claims parseJWT(String jwt) {\n        try {\n            Claims claims = Jwts.parser().setSigningKey(jwtConfig.getKey()).parseClaimsJws(jwt).getBody();\n\n            String username = claims.getSubject();\n            String redisKey = Consts.REDIS_JWT_KEY_PREFIX + username;\n\n            // 校验redis中的JWT是否存在\n            Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS);\n            if (Objects.isNull(expire) || expire <= 0) {\n                throw new SecurityException(Status.TOKEN_EXPIRED);\n            }\n\n            // 校验redis中的JWT是否与当前的一致，不一致则代表用户已注销/用户在不同设备登录，均代表JWT已过期\n            String redisToken = stringRedisTemplate.opsForValue().get(redisKey);\n            if (!StrUtil.equals(jwt, redisToken)) {\n                throw new SecurityException(Status.TOKEN_OUT_OF_CTRL);\n            }\n            return claims;\n        } catch (ExpiredJwtException e) {\n            log.error(\"Token 已过期\");\n            throw new SecurityException(Status.TOKEN_EXPIRED);\n        } catch (UnsupportedJwtException e) {\n            log.error(\"不支持的 Token\");\n            throw new SecurityException(Status.TOKEN_PARSE_ERROR);\n        } catch (MalformedJwtException e) {\n            log.error(\"Token 无效\");\n            throw new SecurityException(Status.TOKEN_PARSE_ERROR);\n        } catch (SignatureException e) {","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/xkcoding/spring-boot-demo/blob/87a142f9604c1a5365b4d24d22c2c11c26a9d5ab/demo-rbac-security/src/main/java/com/xkcoding/rbac/security/util/JwtUtil.java#L80-L116","documentation":"Thrown by JwtUtil.parseJWT when the JWT is considered expired. Two distinct paths produce this: (1) the Redis key holding the JWT (REDIS_JWT_KEY_PREFIX + username) has expired or been deleted (getExpire returns null or <= 0), or (2) the jjwt library's parseClaimsJws throws ExpiredJwtException because the token's own exp claim has passed. Status.TOKEN_EXPIRED (code 5002) wraps the error. The Redis check is a secondary expiry mechanism layered on top of the JWT's built-in expiration.","triggerScenarios":"Any authenticated request whose JWT has exceeded its TTL (jwtConfig.ttl or jwtConfig.remember for 'remember me'), or whose Redis backing key has been removed (e.g., by a prior logout). Also fires if the Redis server is down and getExpire returns null.","commonSituations":"Normal session timeout after the configured TTL; user logged out from another device (Redis key deleted); Redis connection failure causing getExpire to return null; clock skew between token issuance and validation servers; jwtConfig.ttl set very short.","solutions":["On the client, catch the 5002 response and redirect to the login page to obtain a fresh JWT.","Verify Redis connectivity — if Redis is unreachable, all tokens appear expired.","Check jwtConfig.ttl and jwtConfig.remember values in application.yml are appropriate for the use case.","Ensure the Redis key prefix (Consts.REDIS_JWT_KEY_PREFIX) is consistent across create and parse operations."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Client-side: check JWT expiry before making authenticated requests\n// Decode the exp claim (no signature verification needed for client-side check)\ntry {\n    Claims claims = Jwts.parser().parseClaimsJwt(jwt.split(\"\\\\.\")[0] + \".\" + jwt.split(\"\\\\.\")[1] + \".\").getBody();\n    if (claims.getExpiration().before(new Date())) {\n        // Token expired — refresh or redirect to login\n        redirectToLogin();\n        return;\n    }\n} catch (Exception e) {\n    redirectToLogin();\n}","typeGuard":null,"tryCatchPattern":"// Client-side: intercept 5002 token-expired responses\ntry {\n    apiClient.someProtectedResource();\n} catch (SecurityException e) {\n    if (e.getStatus().getCode() == 5002) {\n        // Token expired — redirect to login for a new token\n        redirectToLogin();\n    }\n}","preventionTips":["Implement a token-refresh mechanism or silent re-authentication before the JWT TTL expires.","Monitor Redis connectivity — Redis failure causes all tokens to appear expired.","Keep jwtConfig.ttl and jwtConfig.remember values consistent with user-experience expectations.","Ensure the Redis key prefix is identical across createJWT and parseJWT."],"tags":["jwt","spring-security","redis","token-expiry","authentication","session"],"backgroundTag":null,"analyzedSha":"87a142f9604c1a5365b4d24d22c2c11c26a9d5ab","analyzedAt":"2026-08-14T01:16:58.217Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}