xkcoding/spring-boot-demo · error · SecurityException
5003
5003
Error message
当前用户已在别处登录,请尝试更改密码或重新登录!
What it means
parseJWT compares the presented jwt to the one stored in Redis under REDIS_JWT_KEY_PREFIX+username; a mismatch throws SecurityException(Status.TOKEN_OUT_OF_CTRL, code 5003) at README:233 (impl JwtUtil.java:103-104). This enforces single-session-per-account: a newer login overwrote the stored token, or the user logged out.
Source
Thrown at demo-rbac-security/README.md:233
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);
} catch (SignatureException e) {
log.error("无效的 Token 签名");
throw new SecurityException(Status.TOKEN_PARSE_ERROR);
} catch (IllegalArgumentException e) {
log.error("Token 参数不存在");
throw new SecurityException(Status.TOKEN_PARSE_ERROR);
}View on GitHub (pinned to 87a142f960)
Solutions
- Inform the user another session took over and prompt re-login or password change
- If concurrent sessions are desired, store a list of valid tokens per user instead of one
- Track device/client info so the message can name the offending session
Defensive patterns
Strategy: try-catch
Validate before calling
String stored = stringRedisTemplate.opsForValue().get(Consts.REDIS_JWT_KEY_PREFIX + username);
if (!StrUtil.equals(token, stored)) {
// another session took over - prompt re-login before the aspect throws
} Try / catch
try { jwtUtil.parseJWT(token); }
catch (SecurityException e) { if (Integer.valueOf(5003).equals(e.getCode())) { /* TOKEN_OUT_OF_CTRL - logged in elsewhere */ } } Prevention
- If concurrent sessions are allowed, store a set of tokens per user
- Communicate the single-session policy clearly to users
- Differentiate this 5003 path from the 5002 expiry path in the UI
When it happens
Trigger: The user logs in on a second device and createJWT overwrites the Redis key, invalidating the first device's token; or invalidateJWT deleted/changed the stored value.
Common situations: A single-session-per-account policy; logging out on one device while actively using another; testing the same account in two browsers.
Related errors
AI-assisted analysis of xkcoding/spring-boot-demo@87a142f960 (2026-08-14).
Data as JSON: /api/errors/90d1d6a9eba7a18a.
Report an issue: GitHub.