xkcoding/spring-boot-demo · warning · SecurityException
401
401
Error message
请先登录!
What it means
Thrown by the logout endpoint when jwtUtil.invalidateJWT(request) raises a SecurityException internally. invalidateJWT calls parseJWT, which throws SecurityException for an expired, malformed, or signature-invalid token. The outer catch re-wraps it as Status.UNAUTHORIZED (401, 'please login first'), collapsing all token errors into a single 'not logged in' message. This is a deliberate obfuscation to avoid leaking token-validation details.
Source
Thrown at demo-rbac-security/src/main/java/com/xkcoding/rbac/security/controller/AuthController.java:61
* 登录
*/
@PostMapping("/login")
public ApiResponse login(@Valid @RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmailOrPhone(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtil.createJWT(authentication, loginRequest.getRememberMe());
return ApiResponse.ofSuccess(new JwtResponse(jwt));
}
@PostMapping("/logout")
public ApiResponse logout(HttpServletRequest request) {
try {
// 设置JWT过期
jwtUtil.invalidateJWT(request);
} catch (SecurityException e) {
throw new SecurityException(Status.UNAUTHORIZED);
}
return ApiResponse.ofStatus(Status.LOGOUT);
}
}
View on GitHub (pinned to 87a142f960)
Solutions
- Handle the 401 response on the client by redirecting to the login page — the logout is moot if the token is already invalid.
- If the token is already expired, the user is effectively logged out server-side (Redis key expired), so a client-side token clear suffices.
- Consider making logout idempotent: catch SecurityException and return Status.LOGOUT success regardless, since the goal (invalidating the session) is already achieved.
- Verify jwtConfig.key has not changed between token issuance and logout.
Example fix
// before — re-throws as UNAUTHORIZED on any token error
} catch (SecurityException e) {
throw new SecurityException(Status.UNAUTHORIZED);
}
// after — logout is idempotent; succeed even if token is already invalid
} catch (SecurityException e) {
log.warn("Logout called with invalid token, treating as already logged out");
}
return ApiResponse.ofStatus(Status.LOGOUT); Defensive patterns
Strategy: try-catch
Validate before calling
// Client-side: check token expiry before calling logout
// Decode the JWT exp claim and compare to current time
long exp = decodeJwtExp(jwt); // client-side JWT decode
if (exp < System.currentTimeMillis() / 1000) {
// Token already expired — just clear client-side state, no need to call logout
clearTokenAndRedirectToLogin();
return;
} Try / catch
// Client-side: handle the 401 from logout
try {
apiClient.logout();
} catch (HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {
// Token is invalid/expired — user is effectively logged out already
clearTokenAndRedirectToLogin();
}
} Prevention
- Make logout idempotent: treat 401 from logout as success (user is already logged out).
- On the client, clear local token state regardless of the logout API response.
- Do not change jwtConfig.key after deployment — existing tokens will all fail validation.
When it happens
Trigger: POST /api/auth/logout with an expired, invalid, or missing JWT in the Authorization header. parseJWT fails (TOKEN_EXPIRED, TOKEN_OUT_OF_CTRL, or TOKEN_PARSE_ERROR), the inner SecurityException propagates to the catch, and the endpoint re-throws UNAUTHORIZED.
Common situations: Session already expired before logout is clicked; token was invalidated from another device (TOKEN_OUT_OF_CTRL); client sends a malformed or tampered token; token signature mismatch due to jwtConfig.key change.
Related errors
AI-assisted analysis of xkcoding/spring-boot-demo@87a142f960 (2026-08-14).
Data as JSON: /api/errors/e64a2c594ee046df.
Report an issue: GitHub.