Почему недостаточная поддержка Exception переопределяет пользовательские исключения JWT в Spring Security?JAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Почему недостаточная поддержка Exception переопределяет пользовательские исключения JWT в Spring Security?

Сообщение Anonymous »

Я работаю над приложением 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>
При использовании регистраторов для отслеживания потока я вижу, что запрос действительно достигает моего пользовательского класса, но исходное сообщение исключения теряется в процессе.

Код: Выделить всё

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);

@Autowired
public JwtAuthenticationFilter(JwtService jwtService, UserDetailsService userDetailsService, TokenBlackListService tokenBlackListService){
this.jwtService = jwtService;
this.userDetailsService = userDetailsService;
this.tokenBlackListService = tokenBlackListService;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

final String authHeader = request.getHeader("Authorization");
final String jwt;
final String username;

if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}

jwt = authHeader.substring(7);

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());

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {

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);

@Autowired
public JwtAuthenticationFilter(JwtService jwtService, UserDetailsService userDetailsService, TokenBlackListService tokenBlackListService) {
this.jwtService = jwtService;
this.userDetailsService = userDetailsService;
this.tokenBlackListService = tokenBlackListService;
}

@Override
protected void doFilterInternal(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull FilterChain filterChain) throws ServletException, IOException {

final String authHeader = request.getHeader("Authorization");
final String jwt;
final String username;

if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}

jwt = authHeader.substring(7);

try {
if (tokenBlackListService.isTokenBlackListed(jwt)) {
logger.info("Token marcado como revocado");
throw new JwtRevokedException("El token fue revocado o invalido");
}

username = jwtService.getUsernameFromToken(jwt);

if (username != null &&  SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);

authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}

filterChain.doFilter(request, response);

} catch (JwtRevokedException | JwtException e) {
request.setAttribute("excepcion", e);
logger.error("Error de autenticación JWT: {}", e.getMessage());
filterChain.doFilter(request, response);
} catch (Exception e) {
request.setAttribute("excepcion", e);
filterChain.doFilter(request, response);
}
}
}
< /code>
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

private final Logger logger = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);
private final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {

Exception exception = (Exception) request.getAttribute("excepcion");
String path = request.getRequestURI();
String errorMessage;

logger.info("Entering CustomAuthenticationEntryPoint for path: {}, Exception: {}", path,
exception != null ? exception.getClass().getSimpleName() : "null");
logger.error("Authentication error at {}: {}", path, exception != null ? exception.getMessage() : authException.getMessage(),
exception != null ? exception : authException);

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
}
Это действительно плохое решение?


Подробнее здесь: https://stackoverflow.com/questions/797 ... s-in-sprin
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»