I'm creating a component class that overrides a reactive method that calls another microservice "uaa" that validates a token, but when I verify that the token is invalid I throw an exception, but that exception does not catch in my exception controller handler here is my component class
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthFilter implements GlobalFilter {
private final JwtTokenProviderService jwtTokenProviderService;
private final TokenStatusDaoService tokenStatusDaoService;
private final WebClient webClient;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("something in the way");
List<String> headers = exchange.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION);
if(CollectionUtils.isEmpty(headers)) {
log.trace("Request came without token");
return chain.filter(exchange);
} else {
String authToken = headers.get(0);
log.trace("Request holds a token");
log.debug("Check if token has expired ...");
if(jwtTokenProviderService.isTokenExpired(authToken)) {
log.debug("Token has expired will throw an error");
throw new AuthorizationForbiddenException(AuthorizationForbiddenExceptionTitleEnum.TOKEN_HAS_EXPIRED, "Token has expired");
}else {
log.debug("Check if token is valid and already saved");
String userId = jwtTokenProviderService.getClaimsFromToken(authToken).get(SecurityUtils.IDENTIFIER_KEY).toString();
if(!tokenStatusDaoService.exists(TokenStatusSpecification.withToken(authToken).and(TokenStatusSpecification.withUserId(Long.parseLong(userId))))) {
return webClient.get()
.uri("http://uaa", uriBuilder -> uriBuilder
.path("/validate-token")
.queryParam("token", authToken).build()).retrieve()
.bodyToMono(TokenValidationGetResource.class)
.map(tokenValidationGetResource -> {
if (!tokenValidationGetResource.isValid()) {
log.debug("token is not valid");
throw new AuthorizationForbiddenException(AuthorizationForbiddenExceptionTitleEnum.TOKEN_NOT_VALID, "Token is not valid");
} else {
log.debug("token is valid");
TokenStatusEntity tokenStatusEntity;
try {
tokenStatusEntity = tokenStatusDaoService.findOne(TokenStatusSpecification.withUserId(Long.parseLong(userId)));
} catch (Exception e) {
log.debug("No token defined for user: {}. Will save a new one ...", userId);
tokenStatusEntity = new TokenStatusEntity();
}
tokenStatusEntity.setToken(authToken);
tokenStatusEntity.setUserId(Long.parseLong(userId));
tokenStatusEntity.setStatus(TokenStatusEnum.VALID);
tokenStatusDaoService.save(tokenStatusEntity);
log.debug("Token status entity: {}", tokenStatusEntity);
return exchange;
}
}).flatMap(chain::filter);
} else {
log.debug("Token exists in DB");
return chain.filter(exchange);
}
}
}
}
}
and here is my exception controller handler:
@ControllerAdvice
public class ExceptionControllerImpl implements ExceptionController {
@Override
@ExceptionHandler({
AuthorizationForbiddenException.class
})
public ResponseEntity<ErrorDetailResource> handleGenericExceptions(
AbstractBaseException e, HttpServletRequest request) {
ErrorDetailResource errorDetailResource = new ErrorDetailResource();
errorDetailResource.setTimestamp(Instant.now().toEpochMilli());
errorDetailResource.setTitle(e.getTitle().toString());
errorDetailResource.setCode(e.getTitle().getCode());
errorDetailResource.setDeveloperMessage(e.getClass().getName());
errorDetailResource.setStatus(e.getStatus().value());
errorDetailResource.setDetail(e.getMessage());
return new ResponseEntity<>(errorDetailResource, e.getStatus());
}
}
Hello Those exceptions are thrown on a mono method in a reactive manner, so they can not be caught by controller advice, instead of doing that create a class which will extends the abstract class AbstractErrorWebExceptionHandler
@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public GlobalErrorWebExceptionHandler(GlobalErrorAttributes globalErrorAttributes,
ApplicationContext applicationContext,
ServerCodecConfigurer serverCodecConfigurer) {
super(globalErrorAttributes, new WebProperties.Resources(), applicationContext);
super.setMessageWriters(serverCodecConfigurer.getWriters());
super.setMessageReaders(serverCodecConfigurer.getReaders());
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
final Map<String, Object> errorPropertiesMap = getErrorAttributes(request, ErrorAttributeOptions.defaults());
Throwable error = null;
// here is your abstract base exception
AbstractBaseException baseException = null;
try {
baseException = (AbstractBaseException) getError(request);
} catch (Exception e) {
error = getError(request);
}
HttpStatus statusCode = baseException != null ? baseException.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
return ServerResponse.status(statusCode)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
}
And of course do not forget to add DefaultErrorAttributes
@Component
public class GlobalErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
Throwable error = null;
// here is your abstract base exception
// cast the error to your exception class
AbstractBaseException baseException = null;
try {
baseException = (AbstractBaseException) getError(request);
} catch (Exception e) {
error = getError(request);
}
Map<String, Object> errorResources = new HashMap<>();
// Define the attribute that you want to return in response body
errorResources.put("attribute1", Instant.now().toEpochMilli());
errorResources.put("attribute2", baseException != null ? baseException.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR);
return errorResources;
}
}