Я работаю над приложением Spring Boot с Spring Security (6.x/7.x), используя аутентификацию JWT. У меня есть jwtauthenticationfilter, который подтверждает токены и бросает исключения, возникающие из библиотеки JJWT, которые охватывают аутентификацию, такие как BadcredentialSexception («Токен JWT истек» или «токен был отменен») или аутентификация, оценка, коэффициент, «InvageDID JWT -сигнала"). Я хочу, чтобы эти исключения достигли моей CustomAuthenticationEntryPoint, поэтому я могу вернуть ответ JSON с их сообщениями. Однако для любой ошибки (неверная, истекшая, отозванная или отсутствующая токен), я получаю недостаточное обеспечение для обеспечения общего сообщения «Полная аутентификация необходима для доступа к этому ресурсу» и Path /error.
{
"timestamp": "2025-09-18T13:08:24",
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource",
"path": "/error",
"internalCode": "AUTH_ERROR",
"details": null
}
< /code>
При использовании регистраторов для отслеживания потока я вижу, что запрос действительно достигает моего пользовательского класса, но исходное сообщение исключения теряется в процессе.
Я работаю над приложением Spring Boot с Spring Security (6.x/7.x), используя аутентификацию JWT. У меня есть jwtauthenticationfilter, который подтверждает токены и бросает исключения, возникающие из библиотеки JJWT, которые охватывают аутентификацию, такие как BadcredentialSexception («Токен JWT истек» или «токен был отменен») или аутентификация, оценка, коэффициент, «InvageDID JWT -сигнала"). Я хочу, чтобы эти исключения достигли моей CustomAuthenticationEntryPoint, поэтому я могу вернуть ответ JSON с их сообщениями. Однако для любой ошибки (неверная, истекшая, отозванная или отсутствующая токен), я получаю недостаточное обеспечение для обеспечения общего сообщения «Полная аутентификация необходима для доступа к этому ресурсу» и Path /error. { "timestamp": "2025-09-18T13:08:24", "status": 401, "error": "Unauthorized", "message": "Full authentication is required to access this resource", "path": "/error", "internalCode": "AUTH_ERROR", "details": null } < /code> При использовании регистраторов для отслеживания потока я вижу, что запрос действительно достигает моего пользовательского класса, но исходное сообщение исключения теряется в процессе.[code]2025-09-18T13:08:24.210-06:00 INFO [proyecto] c.p.s.e.CustomAuthenticationEntryPoint : Entrando en CustomAuthenticationEntryPoint para path: /error, Excepción: InsufficientAuthenticationException < /code> Мой пользовательский фильтр: < /p> @Component public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService; private final UserDetailsService userDetailsService; private final TokenBlackListService tokenBlackListService; private final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
try { //Validation and security context assignment logic.
} catch (ExpiredJwtException e) { logger.warn("JWT expirado: {}", e.getMessage()); throw new BadCredentialsException("Token JWT ha expirado", e); // Wrap para preservar stack trace
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException e) { logger.warn("JWT inválido: {}", e.getMessage()); throw new AuthenticationCredentialsNotFoundException("Firma o estructura de JWT inválida", e);
} catch (JwtException e) { // Catch-all para otras JwtExceptions logger.error("Error en validación JWT: {}", e.getMessage(), e); throw new AuthenticationServiceException("Fallo en servicio de autenticación JWT", e);
} catch (Exception e) { // Para errores no-JWT, e.g., DB issues en UserDetailsService logger.error("Error inesperado en autenticación JWT: {}", e.getMessage(), e); throw new AuthenticationServiceException("Error interno en autenticación", e); }
filterChain.doFilter(request, response); // Solo si éxito
} } < /code> Моя аутентификация.@Component public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class); private final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
String path = request.getRequestURI(); String errorMessage = authException.getMessage(); // Usar mensaje del filtro directamente HttpStatus httpStatus = authException instanceof AuthenticationServiceException ? HttpStatus.INTERNAL_SERVER_ERROR // 500 para errores internos : HttpStatus.UNAUTHORIZED; // 401 para otros casos
logger.error("Error de autenticación en {}: {}", path, errorMessage, authException);
ErrorResponse errorResponse = new ErrorResponse( httpStatus, errorMessage, path, "AUTH_ERROR" );
response.setContentType("application/json"); response.setStatus(httpStatus.value()); try (var writer = response.getWriter()) { String jsonResponse = objectMapper.writeValueAsString(errorResponse); logger.debug("Respondiendo con JSON para {}: {}", path, jsonResponse); writer.write(jsonResponse); writer.flush(); } catch (IOException e) { logger.error("Fallo al serializar o escribir la respuesta JSON: {}", e.getMessage(), e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error al procesar la respuesta"); } } } < /code> До этого момента он не работает так, как я хочу: исключения, пойманные в начале, теряются, как показано в журналах. Ни исключение, ни его сообщение не отражаются; Вместо этого используется один по умолчанию. На данный момент это единственное решение, которое работает для меня, но я не уверен, правильно ли я обращаюсь с ошибками или могу ли я раскрывать больше информации, чем я должен.@Component public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService; private final UserDetailsService userDetailsService; private final TokenBlackListService tokenBlackListService; private final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
try { if (tokenBlackListService.isTokenBlackListed(jwt)) { logger.info("Token marcado como revocado"); throw new JwtRevokedException("El token fue revocado o invalido"); }
private final Logger logger = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class); private final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
if (exception != null) { logger.info("Handling exception thrown by JWT filter: {}", exception.getClass().getSimpleName()); errorMessage = exception.getMessage(); } else { logger.info("Handling Spring Security exception: {}", authException.getClass().getSimpleName()); errorMessage = authException.getMessage(); }
ErrorResponse errorResponse = new ErrorResponse( HttpStatus.UNAUTHORIZED, errorMessage, path, "AUTH_ERROR" );
response.setContentType("application/json"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); try (var writer = response.getWriter()) { String jsonResponse = objectMapper.writeValueAsString(errorResponse); logger.info("Responding with JSON for {}: {}", path, jsonResponse); writer.write(jsonResponse); writer.flush(); } catch (IOException e) { logger.error("Failed to serialize or write JSON response: {}", e.getMessage(), e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error processing response"); } } } < /code> Здесь он отображает исходное исключение и его сообщение.2025-09-18T13:27:10.362-06:00 INFO 26984 --- [proyecto] [nio-8080-exec-4] c.p.s.e.CustomAuthenticationEntryPoint : Manjeando excepcion lanzada por el filtro JWT: SignatureException 2025-09-18T13:27:10.364-06:00 INFO 26984 --- [proyecto] [nio-8080-exec-4] c.p.s.e.CustomAuthenticationEntryPoint : Respondiendo con JSON para /usuario: {"timestamp":"2025-09-18T13:27:10","status":401,"error":"Unauthorized","message":"JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.","path":"/usuario","internalCode":"AUTH_ERROR","details":null} < /code> { "timestamp": "2025-09-18T13:27:10", "status": 401, "error": "Unauthorized", "message": "JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.", "path": "/usuario", "internalCode": "AUTH_ERROR", "details": null } [/code] Что было бы лучшим решением этой проблемы, не выходя против принципов безопасности пружины?