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

  1. Handle the 401 response on the client by redirecting to the login page — the logout is moot if the token is already invalid.
  2. If the token is already expired, the user is effectively logged out server-side (Redis key expired), so a client-side token clear suffices.
  3. Consider making logout idempotent: catch SecurityException and return Status.LOGOUT success regardless, since the goal (invalidating the session) is already achieved.
  4. 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

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.