How can we postHandle an exception in HandlerInterceptorAdapter? - java

I am currently trying to implement a customized error handler for spring boot and I have done it with the following:
public class ExceptionHandler extends HandlerInterceptorAdapter {
public static Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, #Nullable ModelAndView modelAndView) throws Exception {
try {
log.info("Service {} Calling {} on {} finished with status {}",request.getRemoteUser(), request.getMethod(), request.getRequestURI(), HttpStatus.valueOf(response.getStatus()));
} catch (Exception e) {
// Do nothing
} finally {
log.error("[Spring Boot Interceptor] {} returned with {}", handler, HttpStatus.valueOf(response.getStatus()));
}
}
Somehow this does not work, and the exception is still thrown to the client, is there some way to catch the exception thrown by the method and ignore it for example.

A good way to manage the exception is using #ControllerAdvice, using this you may handle any kind of exception and customize the response as required.
As said in the comment, you have to add InterceptorRegistry to register the interceptor.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Interceptor()).addPathPatterns("/**");
}
}
The catch block inside postHandle will only be executed if an exception occurred inside the try-catch block as below,
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, #Nullable ModelAndView modelAndView) throws Exception {
try {
int error = 1/0;
} catch (Exception e) {
log.info("Exception will be handled inside catch block");
}
}
Now let's explore the #ControllerAdvice to manage the exception within the application. These two APIs will generate the exceptions and we will manage the exceptions using #ExceptionHandler
#GetMapping("/exception/404")
public void generateResourceNotFound() {
throw new ResourceNotFoundException("resource not found");
}
#GetMapping("/exception/403")
public void generateAccessDenied() {
throw new AccessDeniedException("access denied");
}
GlobalExceptionHandler.java
import com.learning.annotations.controller.ResourceNotFoundException;
import com.learning.annotations.dto.ErrorResponseDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
public Logger log = LoggerFactory.getLogger(Interceptor.class);
#ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponseDTO> handleAccessDeniedException(AccessDeniedException ex, WebRequest request) {
ErrorResponseDTO response = new ErrorResponseDTO();
response.setError(ex.getMessage());
response.setMessage("You don't have authority to access the resource");
return new ResponseEntity<>(response, HttpStatus.FORBIDDEN);
}
#ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponseDTO> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorResponseDTO response = new ErrorResponseDTO();
response.setError(ex.getMessage());
response.setMessage("Resource might be moved temporary or not available");
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
}
To customize the response we can create error response DTO as follows,
import lombok.Data;
#Data
public class ErrorResponseDTO {
private String message;
private String error;
}

Related

Spring boot Interceptor dont show #ControllerAdvice errorhandler?

Implemetation: Im trying to implementin project with Interceptor but the Error handling in showing only in Terminal which is good but i need also to show in restapi in postman and its showing empty.
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class WebInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(WebInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
logger.error("WebInterceptor preHandle is now logged");
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
logger.error("WebInterceptor posthandle is now logged");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
logger.error("WebInterceptor afterCompletion is now logged");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
public class AppConfig implements WebMvcConfigurer{
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new WebInterceptor()).addPathPatterns("/cart/**").order(1);
registry.addInterceptor(new WebInterceptor()).addPathPatterns("/product/**").order(2);
}
}
import com.kongapigateway.KongAPIgateway.ModelException.DATE_FORMAT_ERROR;
import com.kongapigateway.KongAPIgateway.ModelException.ProductExecption;
import com.kongapigateway.KongAPIgateway.ModelException.ProductIDnotFound;
import com.kongapigateway.KongAPIgateway.ModelException.ProductValueNotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
#RestControllerAdvice
public class WEbAspectExceptionConfig {
private Logger logger = LoggerFactory.getLogger(WEbAspectExceptionConfig.class);
#ExceptionHandler(ProductValueNotNull.class)
#ResponseStatus
public void handle(ProductValueNotNull e) {
logger.error(e.getMessage());
}
#ExceptionHandler(DATE_FORMAT_ERROR.class)
#ResponseStatus
public void handle2(DATE_FORMAT_ERROR e) {
logger.error(e.getMessage());
}
#ExceptionHandler(ProductExecption.class)
#ResponseStatus
public void handle2(ProductExecption e) {
logger.error(e.getMessage());
}
#ExceptionHandler(ProductIDnotFound.class)
#ResponseStatus
public void handle2(ProductIDnotFound e) {
logger.error(e.getMessage());
}
}
Terminal Output:
This is the error handling message: Please input all field
kongapigateway | 2022-06-07 11:21:33.149 INFO 1 --- [nio-8095-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
kongapigateway | 2022-06-07 11:21:33.161 ERROR 1 --- [nio-8095-exec-1] c.k.K.Interceptor.WebInterceptor : WebInterceptor preHandle is now logged
kongapigateway | 2022-06-07 11:21:33.277 ERROR 1 --- [nio-8095-exec-1] c.k.K.AOP.WEbAspectExceptionConfig : Please input all field
kongapigateway | 2022-06-07 11:21:33.277 ERROR 1 --- [nio-8095-exec-1] c.k.K.Interceptor.WebInterceptor : WebInterceptor afterCompletion is now logged
Postman api error handler is Empty
Shown here
Kindly disregard this Post Question.
I used Spring AOP #Around for customize exception.
Thank you.

How do I handle a Callable in my AOP Around advice?

I have a controller in my Spring Boot application. In my controller, I have an endpoint where I need to timeout the call if too much time elapses. I do this by returning a Callable from this method and including the config spring.mvc.async.request-timeout in my application.yml. This seems to work well for our purposes.
I also have an Aspect class in this application that contains a method that is triggered whenever a method in my controller is called. The point of this method is to log details such as the amount of time taken for an endpoint, what the response code was, and etc. This works well when the response of the method is not a Callable (ie. a ResponseEntity) since I can get response information from the return type without issue. However, I cannot get this response information when the method returns a Callable without invoking ((Callable) ProceedingJoinPoint.proceed()).call() from the aspect class. This makes API calls longer, and I believe that's because it invokes call() twice. Is there any way that I can get the response information without having to use call() in the Aspect class?
Here is a simple example of what I have so far in my aspect class:
#Around("...")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
if (!(result instanceof Callable<?>)) {
// Do logging using result, which is a ResponseEntity...
} else {
Object callableResult = ((Callable<?>) result).call();
// Do logging using callableResult, which is a ResponseEntity...
}
return result;
}
Thank you.
I encountered the same situation at work a while ago and the following seems to solve it: Log response body after asynchronous Spring MVC controller method
Note: Try logging the async response in a class annotated with #ControllerAdvice and implements ResponseBodyAdvice instead. This should capture the real response instead of callable.
You could have a class annotated with #Aspects for logging request and #ControllerAdvice for logging response together.
e.g.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicLong;
#Aspect
#ControllerAdvice
public class LoggingAdvice implements ResponseBodyAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAdvice.class);
private static final AtomicLong ID = new AtomicLong();
#Before("within(com.example.demo.controller..*)")
public void endpointBefore(JoinPoint p) {
LOGGER.info(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
Object[] signatureArgs = p.getArgs();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
if (signatureArgs != null && signatureArgs.length > 0) {
LOGGER.info("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
} else {
LOGGER.info("request object is empty");
}
} catch (JsonProcessingException e) {
}
}
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
long id = ID.incrementAndGet();
ServletServerHttpResponse responseToUse = (ServletServerHttpResponse) response;
HttpMessageConverter httpMessageConverter;
LoggingHttpOutboundMessageWrapper httpOutputMessage = new LoggingHttpOutboundMessageWrapper();
try {
httpMessageConverter = (HttpMessageConverter) selectedConverterType.newInstance();
httpMessageConverter.write(body, selectedContentType, httpOutputMessage);
LOGGER.info("response {}, {}, {}, {}, {}", id, responseToUse.getServletResponse().getStatus(), responseToUse.getServletResponse().getContentType(),
responseToUse.getHeaders(), httpOutputMessage.getResponseBodyInString());
} catch (InstantiationException | IllegalAccessException | IOException e) {
e.printStackTrace();
}
return body;
}
private static final class LoggingHttpOutboundMessageWrapper implements HttpOutputMessage {
private HttpHeaders httpHeaders = new HttpHeaders();
private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
#Override
public OutputStream getBody() throws IOException {
return byteArrayOutputStream;
}
#Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
public String getResponseBodyInString() {
return new String(byteArrayOutputStream.toByteArray());
}
}
}

Spring Boot - handle exception wrapped with BindException

I am looking for a way to handle custom exception thrown during binding of request parameter to DTO field.
I have a cantroller in Spring Boot application as follows
#GetMapping("/some/url")
public OutputDTO filterEntities(InputDTO inputDTO) {
return service.getOutput(inputDTO);
}
input DTO has few fields, one of which is of enum type
public class InputDTO {
private EnumClass enumField;
private String otherField;
/**
* more fields
*/
}
user will hit the URL in ths way
localhost:8081/some/url?enumField=wrongValue&otherField=anyValue
Now if user sends wrong value for enumField, I would like to throw my CustomException with particular message. Process of enum instance creation and throwing of exception is implemented in binder
#InitBinder
public void initEnumClassBinder(final WebDataBinder webdataBinder) {
webdataBinder.registerCustomEditor(
EnumClass.class,
new PropertyEditorSupport() {
#Override
public void setAsText(final String text) throws IllegalArgumentException {
try {
setValue(EnumClass.valueOf(text.toUpperCase()));
} catch (Exception exception) {
throw new CustomException("Exception while deserializing EnumClass from " + text, exception);
}
}
}
);
}
Problem is that when exception is thrown it is impossible to handle it with
#ExceptionHandler(CustomException.class)
public String handleException(CustomException exception) {
// log exception
return exception.getMessage();
}
Spring wraps initial exception with BindException. That instance contains my initial error message, but concatenated with other text which is redundant for me. I don't think that parsing and substringing that message is good...
Am I missing something? What is the proper way to get message from initial
CustomException here?
You will not be able to handle exceptions thrown before entering your controller method by using #ExceptionHandler annotated methods. Spring handles these exceptions before entering the controller, by registering DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver handler.
This is the case of BindingException, thrown when Spring cannot bind request parameters to match your InputDTO object.
What you can do is to register your own handler (create a Component implementing HandlerExceptionResolver and Ordered interfaces), give it the highest priority in handling errors and play with exceptions as needed.
You have also to pay attention to BindException as it wrappes your custom exception, CustomException.class
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import yourpackage.CustomException;
#Component()
public class BindingExceptionResolver implements HandlerExceptionResolver, Ordered {
private static final Logger logger = LoggerFactory.getLogger(BindingExceptionResolver.class);
public BindingExceptionResolver() {
}
private ModelAndView handleException(ObjectError objectError, HttpServletResponse response){
if (objectError == null) return null;
try {
if(objectError.contains(CustomException.class)) {
CustomException ex = objectError.unwrap(CustomException.class);
logger.error(ex.getMessage(), ex);
return handleCustomException(ex, response);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
protected ModelAndView handleCustomException(CustomException ex, HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
return new ModelAndView();
}
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
if (ex instanceof org.springframework.validation.BindException) {
BindException be = (BindException) ex;
logger.debug("Binding exception in {} :: ({}) :: ({})=({})", be.getObjectName(), be.getBindingResult().getTarget().getClass(), be.getFieldError().getField(), be.getFieldError().getRejectedValue());
return be.getAllErrors().stream()
.filter(o->o.contains(Exception.class))
.map(o ->handleException(o, response))
.filter(mv ->mv !=null)
.findFirst().orElse(null);
}
} catch (Exception handlerException) {
logger.error("Could not handle exception", handlerException);
}
return null;
}
#Override
public int getOrder() {
return Integer.MIN_VALUE;
}
}
Hope it helps

request.getRequestURI always returns "/error"

In a webapp I'm building using Spring Boot & MVC and am trying to deny access to for all URL's except /signin for users that are not logged in. To achieve this I've setup an implementation of HandlerInterceptor where the preHandler should route all non-valid requests to the /signin page.
The setup:
LoginViewController
package com.controller;
import com.model.UserDao;
import com.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
#Controller
#RequestMapping(value = "/signin")
#SessionAttributes("username")
public class LoginViewController {
#Autowired
private UserService userService;
#RequestMapping(method = RequestMethod.GET)
public ModelAndView showLoginForm(){
return new ModelAndView("login");
}
#RequestMapping(method = RequestMethod.POST)
public ModelAndView verifyLogin(HttpServletRequest request, HttpSession session) {
ModelAndView modelAndView;
String username = request.getParameter("username");
// if login fails, set reload login page
if (userService.verifyUserLogin(username,request.getParameter("password")) == null){
modelAndView = new ModelAndView("login");
modelAndView.addObject("login_failed", true);
} else {
modelAndView = new ModelAndView("index");
session.setAttribute("username", username);
}
return modelAndView;
}
}
AccessInterceptor
package com.spring.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import java.util.ArrayList;
public class AccessInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println(request.getRequestURI());
try {
if (!request.getRequestURI().endsWith("/signin")) {
if (request.getSession()
.getAttribute("username") == null) {
response.sendRedirect(request.getContextPath() + "/signin");
return false;
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return true;
}
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("Post-handle");
}
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("After completion handle");
}
}
WebApplicationConfig
package com.spring;
import com.spring.interceptor.AccessInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
#Configuration
#EnableWebMvc
public class WebApplicationConfig extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new AccessInterceptor()).
addPathPatterns("/**").
excludePathPatterns("/signin/**").
excludePathPatterns("/static/**");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
WebApplicationInitializer
package com.spring;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
public class MyWebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(WebApplicationConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherContext =
new AnnotationConfigWebApplicationContext();
dispatcherContext.register(MyWebAppInitializer.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcherServlet", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/*");
dispatcher.addMapping("*.css");
dispatcher.addMapping("*.eot");
dispatcher.addMapping("*.svg");
dispatcher.addMapping("*.ttf");
dispatcher.addMapping("*.woff");
dispatcher.addMapping("*.map");
dispatcher.addMapping("*.js");
dispatcher.addMapping("*.ico");
}
}
Now the problem is that the System.out.println(request.getRequestURI()) in AccessInterceptor always prints /error. So the request is always redirected even when calling /signin. Another interesting thing is that no CSS or other static resources are rendered, even with the dispacher mapping configured.
Any Ideas?
if you missing your payload, the api url couldn't recognize. at least put something
I also met this problem,in WebLogIntercept(your MyWebAppInitializer) class
I solved this problem using the following code
add this two func
private Class getClassByName(Class classObject, String name){
Map<Class,List<Field>> fieldMap = new HashMap<>();
Class returnClass = null;
Class tempClass = classObject;
while (tempClass != null) {
fieldMap.put(tempClass,Arrays.asList(tempClass .getDeclaredFields()));
tempClass = tempClass.getSuperclass();
}
for(Map.Entry<Class,List<Field>> entry: fieldMap.entrySet()){
for (Field f : entry.getValue()) {
if(f.getName().equals(name)){
returnClass = entry.getKey();
break;
}
}
}
return returnClass;
}
private Object findCoyoteRequest(Object request) throws Exception {
Class a = getClassByName(request.getClass(),"request");
Field request1 = a.getDeclaredField("request");
request1.setAccessible(true);
Object b = request1.get(request);
if(getClassByName(b.getClass(),"coyoteRequest") == null){
return findCoyoteRequest(b);
}else{
return b;
}
and use this code
Object a = findCoyoteRequest(request);
Field coyoteRequest = a.getClass().getDeclaredField("coyoteRequest");
coyoteRequest.setAccessible(true);
Object b = coyoteRequest.get(a);
Field uriMB = b.getClass().getDeclaredField("uriMB");
uriMB.setAccessible(true);
MessageBytes c = (MessageBytes)uriMB.get(b);
System.out.println(c.getString());
c.getString() is realuri
my english is not good,Hope useful
Disabling CFR worked for me
See https://www.baeldung.com/spring-security-csrf
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
I have the same function to implement as you. And I finally found that there was nothing wrong with this function. The "/error" request actually exists, maybe sent by the servlet container or whatever(i don't know). It exists to show you the exception on the browser.
When I put "/error" to my white-list(I also put paths like "/login" which don't need the authority), the e.printStackTrace(); information just show on the browser.
sometimes,you can try rebuild project,here is my code:
if(request.getRequestURI().startsWith("/user/")) {return true;}
it always return "/error"

How to manage exceptions thrown in filters in Spring?

I want to use generic way to manage 5xx error codes, let's say specifically the case when the db is down across my whole spring application. I want a pretty error json instead of a stack trace.
For the controllers I have a #ControllerAdvice class for the different exceptions and this is also catching the case that the db is stopping in the middle of the request. But this is not all. I also happen to have a custom CorsFilter extending OncePerRequestFilter and there when i call doFilter i get the CannotGetJdbcConnectionException and it will not be managed by the #ControllerAdvice. I read several things online that only made me more confused.
So I have a lot of questions:
Do i need to implement a custom filter? I found the ExceptionTranslationFilter but this only handles AuthenticationException or AccessDeniedException.
I thought of implementing my own HandlerExceptionResolver, but this made me doubt, I don't have any custom exception to manage, there must be a more obvious way than this. I also tried to add a try/catch and call an implementation of the HandlerExceptionResolver (should be good enough, my exception is nothing special) but this is not returning anything in the response, i get a status 200 and an empty body.
Is there any good way to deal with this? Thanks
So this is what I did:
I read the basics about filters here and I figured out that I need to create a custom filter that will be first in the filter chain and will have a try catch to catch all runtime exceptions that might occur there. Then i need to create the json manually and put it in the response.
So here is my custom filter:
public class ExceptionHandlerFilter extends OncePerRequestFilter {
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (RuntimeException e) {
// custom error response class used across my project
ErrorResponse errorResponse = new ErrorResponse(e);
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.getWriter().write(convertObjectToJson(errorResponse));
}
}
public String convertObjectToJson(Object object) throws JsonProcessingException {
if (object == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(object);
}
}
And then i added it in the web.xml before the CorsFilter. And it works!
<filter>
<filter-name>exceptionHandlerFilter</filter-name>
<filter-class>xx.xxxxxx.xxxxx.api.controllers.filters.ExceptionHandlerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>exceptionHandlerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
I wanted to provide a solution based on the answer of #kopelitsa. The main differences being:
Reusing the controller exception handling by using the HandlerExceptionResolver.
Using Java config over XML config
First, you need to make sure, that you have a class that handles exceptions occurring in a regular RestController/Controller (a class annotated with #RestControllerAdvice or #ControllerAdvice and method(s) annotated with #ExceptionHandler). This handles your exceptions occurring in a controller. Here is an example using the RestControllerAdvice:
#RestControllerAdvice
public class ExceptionTranslator {
#ExceptionHandler(RuntimeException.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorDTO processRuntimeException(RuntimeException e) {
return createErrorDTO(HttpStatus.INTERNAL_SERVER_ERROR, "An internal server error occurred.", e);
}
private ErrorDTO createErrorDTO(HttpStatus status, String message, Exception e) {
(...)
}
}
To reuse this behavior in the Spring Security filter chain, you need to define a Filter and hook it into your security configuration. The filter needs to redirect the exception to the above defined exception handling. Here is an example:
#Component
public class FilterChainExceptionHandler extends OncePerRequestFilter {
private final Logger log = LoggerFactory.getLogger(getClass());
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Exception e) {
log.error("Spring Security Filter Chain Exception:", e);
resolver.resolveException(request, response, null, e);
}
}
}
The created filter then needs to be added to the SecurityConfiguration. You need to hook it into the chain very early, because all preceding filter's exceptions won't be caught. In my case, it was reasonable to add it before the LogoutFilter. See the default filter chain and its order in the official docs. Here is an example:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private FilterChainExceptionHandler filterChainExceptionHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class)
(...)
}
}
I come across this issue myself and I performed the steps below to reuse my ExceptionController that is annotated with #ControllerAdvise for Exceptions thrown in a registered Filter.
There are obviously many ways to handle exception but, in my case, I wanted the exception to be handled by my ExceptionController because I am stubborn and also because I don't want to copy/paste the same code (i.e. I have some processing/logging code in ExceptionController). I would like to return the beautiful JSON response just like the rest of the exceptions thrown not from a Filter.
{
"status": 400,
"message": "some exception thrown when executing the request"
}
Anyway, I managed to make use of my ExceptionHandler and I had to do a little bit of extra as shown below in steps:
Steps
You have a custom filter that may or may not throw an exception
You have a Spring controller that handles exceptions using #ControllerAdvise i.e. MyExceptionController
Sample code
//sample Filter, to be added in web.xml
public MyFilterThatThrowException implements Filter {
//Spring Controller annotated with #ControllerAdvise which has handlers
//for exceptions
private MyExceptionController myExceptionController;
#Override
public void destroy() {
// TODO Auto-generated method stub
}
#Override
public void init(FilterConfig arg0) throws ServletException {
//Manually get an instance of MyExceptionController
ApplicationContext ctx = WebApplicationContextUtils
.getRequiredWebApplicationContext(arg0.getServletContext());
//MyExceptionHanlder is now accessible because I loaded it manually
this.myExceptionController = ctx.getBean(MyExceptionController.class);
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
try {
//code that throws exception
} catch(Exception ex) {
//MyObject is whatever the output of the below method
MyObject errorDTO = myExceptionController.handleMyException(req, ex);
//set the response object
res.setStatus(errorDTO .getStatus());
res.setContentType("application/json");
//pass down the actual obj that exception handler normally send
ObjectMapper mapper = new ObjectMapper();
PrintWriter out = res.getWriter();
out.print(mapper.writeValueAsString(errorDTO ));
out.flush();
return;
}
//proceed normally otherwise
chain.doFilter(request, response);
}
}
And now the sample Spring Controller that handles Exception in normal cases (i.e. exceptions that are not usually thrown in Filter level, the one we want to use for exceptions thrown in a Filter)
//sample SpringController
#ControllerAdvice
public class ExceptionController extends ResponseEntityExceptionHandler {
//sample handler
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ExceptionHandler(SQLException.class)
public #ResponseBody MyObject handleSQLException(HttpServletRequest request,
Exception ex){
ErrorDTO response = new ErrorDTO (400, "some exception thrown when "
+ "executing the request.");
return response;
}
//other handlers
}
Sharing the solution with those who wish to use ExceptionController for Exceptions thrown in a Filter.
So, here's what I did based on an amalgamation of the above answers... We already had a GlobalExceptionHandler annotated with #ControllerAdvice and I also wanted to find a way to re-use that code to handle exceptions that come from filters.
The simplest solution I could find was to leave the exception handler alone, and implement an error controller as follows:
#Controller
public class ErrorControllerImpl implements ErrorController {
#RequestMapping("/error")
public void handleError(HttpServletRequest request) throws Throwable {
if (request.getAttribute("javax.servlet.error.exception") != null) {
throw (Throwable) request.getAttribute("javax.servlet.error.exception");
}
}
}
So, any errors caused by exceptions first pass through the ErrorController and are re-directed off to the exception handler by rethrowing them from within a #Controller context, whereas any other errors (not caused directly by an exception) pass through the ErrorController without modification.
Any reasons why this is actually a bad idea?
If you want a generic way, you can define an error page in web.xml:
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/500</location>
</error-page>
And add mapping in Spring MVC:
#Controller
public class ErrorController {
#RequestMapping(value="/500")
public #ResponseBody String handleException(HttpServletRequest req) {
// you can get the exception thrown
Throwable t = (Throwable)req.getAttribute("javax.servlet.error.exception");
// customize response to what you want
return "Internal server error.";
}
}
This is my solution by overriding default Spring Boot /error handler
package com.mypackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* This controller is vital in order to handle exceptions thrown in Filters.
*/
#RestController
#RequestMapping("/error")
public class ErrorController implements org.springframework.boot.autoconfigure.web.ErrorController {
private final static Logger LOGGER = LoggerFactory.getLogger(ErrorController.class);
private final ErrorAttributes errorAttributes;
#Autowired
public ErrorController(ErrorAttributes errorAttributes) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
this.errorAttributes = errorAttributes;
}
#Override
public String getErrorPath() {
return "/error";
}
#RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest aRequest, HttpServletResponse response) {
RequestAttributes requestAttributes = new ServletRequestAttributes(aRequest);
Map<String, Object> result = this.errorAttributes.getErrorAttributes(requestAttributes, false);
Throwable error = this.errorAttributes.getError(requestAttributes);
ResponseStatus annotation = AnnotationUtils.getAnnotation(error.getClass(), ResponseStatus.class);
HttpStatus statusCode = annotation != null ? annotation.value() : HttpStatus.INTERNAL_SERVER_ERROR;
result.put("status", statusCode.value());
result.put("error", statusCode.getReasonPhrase());
LOGGER.error(result.toString());
return new ResponseEntity<>(result, statusCode) ;
}
}
Just to complement the other fine answers provided, as I too recently wanted a single error/exception handling component in a simple SpringBoot app containing filters that may throw exceptions, with other exceptions potentially thrown from controller methods.
Fortunately, it seems there is nothing to prevent you from combining your controller advice with an override of Spring's default error handler to provide consistent response payloads, allow you to share logic, inspect exceptions from filters, trap specific service-thrown exceptions, etc.
E.g.
#ControllerAdvice
#RestController
public class GlobalErrorHandler implements ErrorController {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(ValidationException.class)
public Error handleValidationException(
final ValidationException validationException) {
return new Error("400", "Incorrect params"); // whatever
}
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Exception.class)
public Error handleUnknownException(final Exception exception) {
return new Error("500", "Unexpected error processing request");
}
#RequestMapping("/error")
public ResponseEntity handleError(final HttpServletRequest request,
final HttpServletResponse response) {
Object exception = request.getAttribute("javax.servlet.error.exception");
// TODO: Logic to inspect exception thrown from Filters...
return ResponseEntity.badRequest().body(new Error(/* whatever */));
}
#Override
public String getErrorPath() {
return "/error";
}
}
When you want to test a state of application and in case of a problem return HTTP error I would suggest a filter. The filter below handles all HTTP requests. The shortest solution in Spring Boot with a javax filter.
In the implementation can be various conditions. In my case the applicationManager testing if the application is ready.
import ...ApplicationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class SystemIsReadyFilter implements Filter {
#Autowired
private ApplicationManager applicationManager;
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!applicationManager.isApplicationReady()) {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "The service is booting.");
} else {
chain.doFilter(request, response);
}
}
#Override
public void destroy() {}
}
After reading through different methods suggested in the above answers, I decided to handle the authentication exceptions by using a custom filter. I was able to handle the response status and codes using an error response class using the following method.
I created a custom filter and modified my security config by using the addFilterAfter method and added after the CorsFilter class.
#Component
public class AuthFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//Cast the servlet request and response to HttpServletRequest and HttpServletResponse
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// Grab the exception from the request attribute
Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
//Set response content type to application/json
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
//check if exception is not null and determine the instance of the exception to further manipulate the status codes and messages of your exception
if(exception!=null && exception instanceof AuthorizationParameterNotFoundException){
ErrorResponse errorResponse = new ErrorResponse(exception.getMessage(),"Authetication Failed!");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = httpServletResponse.getWriter();
writer.write(convertObjectToJson(errorResponse));
writer.flush();
return;
}
// If exception instance cannot be determined, then throw a nice exception and desired response code.
else if(exception!=null){
ErrorResponse errorResponse = new ErrorResponse(exception.getMessage(),"Authetication Failed!");
PrintWriter writer = httpServletResponse.getWriter();
writer.write(convertObjectToJson(errorResponse));
writer.flush();
return;
}
else {
// proceed with the initial request if no exception is thrown.
chain.doFilter(httpServletRequest,httpServletResponse);
}
}
public String convertObjectToJson(Object object) throws JsonProcessingException {
if (object == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(object);
}
}
SecurityConfig class
#Configuration
public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
AuthFilter authenticationFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(authenticationFilter, CorsFilter.class).csrf().disable()
.cors(); //........
return http;
}
}
ErrorResponse class
public class ErrorResponse {
private final String message;
private final String description;
public ErrorResponse(String description, String message) {
this.message = message;
this.description = description;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}}
You can use the following method inside the catch block:
response.sendError(HttpStatus.UNAUTHORIZED.value(), "Invalid token")
Notice that you can use any HttpStatus code and a custom message.
I had the same issue in webflux, going on the theme that someone is looking to resuse there #ControllerAdvice, you do not want to throw a direct exception or return a mono error in the webfilter, however you want to set the response to be the mono error.
public class YourFilter implements WebFilter {
#Override
public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
exchange.getResponse().writeWith(Mono.error(new YouException()));
return chain.filter(exchange)
}
}
In Filters, we don't have a control with #ControllerAdvice or #RestControllerAdvice to handle our exceptions that could occur at the time of doing the authentication. Because, DispatcherServlet will only come into picture after the Controller class hits.
So, we need to do the following.
we need to have
HttpServletResponse httpResponse = (HttpServletResponse) response;
"response" object we can pass it from public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) of GenericFilterBean.java implementation class.
2) We can use the below utility class to write or print our error JSON model or String object into the ServletResponse output stream.
public static void handleUnAuthorizedError(ServletResponse response,Exception e)
{
ErrorModel error = null;
if(e!=null)
error = new ErrorModel(ErrorCodes.ACCOUNT_UNAUTHORIZED, e.getMessage());
else
error = new ErrorModel(ErrorCodes.ACCOUNT_UNAUTHORIZED, ApplicationConstants.UNAUTHORIZED);
JsonUtils jsonUtils = new JsonUtils();
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
try {
httpResponse.getOutputStream().println(jsonUtils.convertToJSON(error));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public String convertToJSON(Object inputObj) {
ObjectMapper objectMapper = new ObjectMapper();
String orderJson = null;
try {
orderJson = objectMapper.writeValueAsString(inputObj);
}
catch(Exception e){
e.printStackTrace();
}
return orderJson;
}
Late to the party but we can also use it like this:
#ApiIgnore
#RestControllerAdvice
public class ExceptionHandlerController {
#Autowired
private MessageSource messageSource;
And in the filter:
#Component
public class MyFilter extends OncePerRequestFilter {
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver exceptionResolver;
#Override
protected void doFilterInternal(HttpServletRequest request, #NotNull HttpServletResponse response, #NotNull FilterChain filterChain) {
try {
// Some exception
} catch (Exception e) {
this.exceptionResolver.resolveException(request, response, null, e);
}
}
Global Default Exception Handlers will work only at Controller or Service level. They will not work at filter level. I found below solution working fine with Spring Boot Security - JWT filter
https://www.jvt.me/posts/2022/01/17/spring-servlet-filter-error-handling/
Below is the snippet I added
httpServletResponse.setContentType("application/json");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpServletResponse.getWriter().write("{\"error\":\"invalid_token\",\"error_description\":\"Invalid Token\"}");
You do not need to create a custom Filter for this. We solved this by creating custom exceptions that extend ServletException (which is thrown from the doFilter method, shown in the declaration). These are then caught and handled by our global error handler.
edit: grammar
It's strange because #ControllerAdvice should works, are you catching the correct Exception?
#ControllerAdvice
public class GlobalDefaultExceptionHandler {
#ResponseBody
#ExceptionHandler(value = DataAccessException.class)
public String defaultErrorHandler(HttpServletResponse response, DataAccessException e) throws Exception {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
//Json return
}
}
Also try to catch this exception in CorsFilter and send 500 error, something like this
#ExceptionHandler(DataAccessException.class)
#ResponseBody
public String handleDataException(DataAccessException ex, HttpServletResponse response) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
//Json return
}

Categories