I have a spirngboot app, I use JWT for the security, so I have in services this block of code
public void registrarUsuario(RegisterDTO registerDTO){
if (authRepository.existsByEmail(registerDTO.getEmail())){
throw new UserAlreadyRegisterException();
}
Usuarios usuario = new Usuarios();
usuario.setPrimer_nombre(registerDTO.getPrimer_nombre());
usuario.setSegundo_nombre(registerDTO.getSegundo_nombre());
usuario.setPrimer_apellido(registerDTO.getPrimer_apellido());
usuario.setSegundo_apellido(registerDTO.getSegundo_apellido());
usuario.setEmail(registerDTO.getEmail());
usuario.setTelefono(registerDTO.getTelefono());
Credenciales credencial = new Credenciales();
credencial.setContraseña(passwordEncoder.encode(registerDTO.getContraseña()));
credencialesRepository.save(credencial);
usuario.setCredenciales(credencial);
Roles roles = rolRepository.findByNombre("usuario").orElseThrow(() -> new NoSuchElementException("No se encontro el rol usuarios"));
usuario.setRoles(Collections.singletonList(roles));
authRepository.save(usuario);
}
When I try to register a new user, if the user already exists, I create the exception UserAlready… and show the correct format in the response
{
"timeStamp": "2024-09-27T04:07:37.495+00:00",
"code": "p-500",
"message": "El usuario ya se encuentra registrado en la aplicacion",
"url": "/api/v1/auth/register"
}
However, when I try to create an exception when token is expired in another endpoint, my global handling doesn’t catch this exception and it only shows in my console.
This is my code
My exception handler:
package com.api.reservavuelos.Exceptions;
import com.api.reservavuelos.DTO.ResponseExceptionDTO;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Date;
@RestControllerAdvice
public class GlobalExceptionHandler {
private Date tiempoactual = new Date();
@ExceptionHandler(JwtTokenExpiredException.class)
public ResponseEntity<ResponseExceptionDTO> handleJwtTokenExpiredException(HttpServletRequest request, JwtTokenExpiredException exception){
return new ResponseEntity<>(new ResponseExceptionDTO(tiempoactual,"P-401", exception.getMessage(), request.getRequestURI()), HttpStatus.UNAUTHORIZED);
}
@ExceptionHandler(AuthenticationCredentialsNotFoundException.class)
public ResponseEntity<ResponseExceptionDTO> handleAuthenticationCredentialsNotFoundException(HttpServletRequest request , AuthenticationCredentialsNotFoundException exception){
return new ResponseEntity<>(new ResponseExceptionDTO(tiempoactual,"P-401", exception.getMessage(), request.getRequestURI()), HttpStatus.UNAUTHORIZED);
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ResponseExceptionDTO> handleUserNotFoundException(HttpServletRequest request, UserNotFoundException exception) {
return new ResponseEntity<>(new ResponseExceptionDTO(tiempoactual,"P-404", exception.getMessage(), request.getRequestURI()), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(UserAlreadyRegisterException.class)
public ResponseEntity<ResponseExceptionDTO> handleUserAlreadyRegisterException(HttpServletRequest request, UserAlreadyRegisterException exception) {
return new ResponseEntity<>(new ResponseExceptionDTO(tiempoactual,"p-500", exception.getMessage(), request.getRequestURI()), HttpStatus.BAD_REQUEST);
}
}
My JwtTokenProvider
package com.api.reservavuelos.Security;
import com.api.reservavuelos.Exceptions.JwtTokenExpiredException;
import com.api.reservavuelos.Exceptions.UserAlreadyRegisterException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JwtTokenProvider {
public String generateToken(Authentication authentication) {
// Implementación JWT Token generación
String username = authentication.getName();
Date tiempoactual = new Date();
Date expiracion = new Date(tiempoactual.getTime() + ConstantSecurity.JWT_EXPIRATION_TOKEN);
String Token = Jwts.builder()
.setSubject(username)
.setIssuedAt(tiempoactual)
.setExpiration(expiracion)
.signWith(SignatureAlgorithm.HS512, ConstantSecurity.JWT_FIRMA)
.compact();
return Token;
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(ConstantSecurity.JWT_FIRMA)
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public Boolean IsValidToken(String token) {
try {
Jwts.parser()
.setSigningKey(ConstantSecurity.JWT_FIRMA)
.build()
.parseClaimsJws(token);
return true;
}catch (ExpiredJwtException e) {
throw new JwtTokenExpiredException("Jwt ha expirado");
} catch (AuthenticationCredentialsNotFoundException e) {
throw new AuthenticationCredentialsNotFoundException("Jwt esta incorrecto");
}
}
}
1
I am not sure how your security filters are configured, but my experience is that the security filter which calls JWT processing is invoked before the MVC takes over and the ControllerAdvice
comes into picture.
My solution (but I had a custom security filter) was to catch the JWT exception in the filter and write a 401 with error message directly into the response. Something like:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
// Process JWT and pass execute on if JWT is valid
} catch (JwtException e) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
objectMapper.writeValue(response.getOutputStream(),
ErrorInfo.createWithMessage(e.getMessage(), request.getRequestURI()));
}
}
0