How to handle exception thrown by aspect using custom handler - java

I'm fighting with AOP and custom error handler.
I have one controller "WorkingController" - it is working as expected.
#Controller
public class WorkingController extends BaseController {
​
#RequestMapping("/welcome")
public ModelAndView helloWorld() {
return new ModelAndView("welcome", "message", "<h3>********** Hello World, Success!</h3>");
}
​
#RequestMapping("/success")
public void test(HttpServletResponse response) {
writeResponse(response, "Success!", ContentType.TEXT_JSON);
}
​
#RequestMapping("/test")
public void test2(HttpServletResponse response) throws IOException {
throw new IOException("This exception should be handled by custom handler.");
}
​
#InterestingMethod
#RequestMapping("/aspect")
public void test3(#InterestingArg #RequestParam(value = "id", required = false) String id,
HttpServletResponse response) throws IOException, AccessDeniedException {
throw new IOException("This exception should also be handled by custom handler.");
}
}
​
class BaseController {
#ExceptionHandler(Exception.class)
public void handleException(Throwable ex, HttpServletRequest request, HttpServletResponse response) {
System.out.println("Exception handled by BaseController, great work!: " + ex.getMessage());
ServletUtil.writeResponse(response, JSONObject.fromObject("Error").toString(),
ContentType.TEXT_JSON.toString(), false, "UTF-8");
}
​
void writeResponse(HttpServletResponse response, String data, ContentType contentType) {
ServletUtil.writeResponse(response, data, contentType.toString(), false, "UTF-8");
}
​
}
My case forces me to create interface, mark my controller and Aspect will check if user has access to given endpoint - so I have MarkedController interface and NotWorkingController.
#Controller
public class NotWorkingController extends BaseController implements MarkedController {
public void test2(HttpServletResponse response) throws IOException {
throw new IOException("NotWorkingController, This exception should be handled by custom handler.");
}
​
#InterestingMethod
public void test3(#InterestingArg String id,
HttpServletResponse response) throws IOException, AccessDeniedException {
throw new IOException("NotWorkingController, This exception should also be handled by custom handler.");
}
}
​
#Controller
public interface MarkedController {
​
#RequestMapping("/test2")
void test2(HttpServletResponse response) throws IOException;
#RequestMapping("/aspect2")
void test3(#RequestParam(value = "id", required = false) String id,
HttpServletResponse response) throws IOException, AccessDeniedException;
}
#Aspect
public class InterestingMethodAspect {
#Pointcut("#annotation(interestingMethod)")
public void interestingMethodPointcut(InterestingMethod interestingMethod) {
}
#Before("(interestingMethodPointcut(interestingMethod))")
public void asd(JoinPoint joinPoint, InterestingMethod interestingMethod) throws AccessDeniedException {
processArguments(joinPoint);
}
private void processArguments(JoinPoint joinPoint) throws AccessDeniedException {
throw new AccessDeniedException("You don't have access to this endpoint.");
}
}
And this is kind of magic for me, because:
In NotWorkingController exception thrown by Aspect code is not handled by custom exception handler (located in BaseController).
I'm forced to move all #RequestMappings to interface - it's a little bit annoying in my case. How to prevent that?
So, where should i look, what should I check?
Thanks in advance for any suggestions.
BTW: Complete maven project is available here.

Yhm, and I have the answers.
For the first problem (exception catching):
HandlerExceptionResolver did all what I needed.
I've removed #ExceptionHandler annotation from handleException method in BaseController and custom implementantion of HandlerExceptionResolver is catching all my exceptions).
Second problem:
I had to switch from JDK-based proxies to CGLIB-based one.

Related

Spring boot interceptor to capture request values on bean validation

I have a spring boot app(v2.3.0.RELEASE) and I need to get any request being sent from my restcontroller when there is a bean validation error.
My Request is as follows:
public class PaymentRequest {
#Valid
private PaymentIdentificationRequest paymentIdentification;
#NotBlank(message = "transactionTypeCode.required")
private String transactionTypeCode;
#NotBlank(message = "name.required")
private String name;
}
For instance, if name is null, I need an interceptor to capture values of transactionTypeCode and
paymentIdentification before exception is triggered.
I tried implementing the following interceptor to capture all not null parameters value being sent:
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest requestServlet, HttpServletResponse responseServlet, Object handler) throws Exception {
//capture required valued set it in HttpServletRequest attribute to be used for exception handling
HandlerMethod h1 = (HandlerMethod) handler;
MethodParameter[] param = null;
System.out.println("MINIMAL: INTERCEPTOR PREHANDLE CALLED");
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
Enumeration<?> e = request.getParameterNames();
System.out.println("MINIMAL: INTERCEPTOR POSTHANDLE CALLED");
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
Enumeration<?> e = request.getParameterNames();
System.out.println("MINIMAL: INTERCEPTOR AFTERCOMPLETION CALLED");
}
When the name is null it enters the method preHandle but I am not able to get the parameters and its corresponding values being sent, any idea how to do it pls?
The reason why I am doing the above changes is because I need to set the value of transactionTypeCode and paymentIdentification in my interceptor above so as to use them below in my exception handler as follows:
#ControllerAdvice
public class RestControllerExceptionHandler extends ResponseEntityExceptionHandler {
private #Autowired
HttpServletRequest httpServletRequest;
#Override
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers,
HttpStatus status, WebRequest request) {
log.error(exception.getMessage(), exception);
// mapParam is size zero
Map<String, String[]> mapParam = httpServletRequest.getParameterMap();
if (!ObjectUtils.isEmpty(exception) && !ObjectUtils.isEmpty(this.request1)) {
paymentValidator.onErrorUpdatePayment(this.request1.getAttribute("transactionTypeCode"), this.request1.getAttribute("paymentIdentification "), exception.toString());
}
....
return new ResponseEntity<>(ipsResponse, new HttpHeaders(), ipsResponse.getHttpStatus());
}

Java Spring boot - OnceRequestPerFilter allow only controller requestmappings

I'm currently implementing audit trail in my project, I tried using HandlerInterceptor and it seems it won't work in my project, so i looked for another way and I discovered that it's possible with OncePerRequestFilter.
Here's the code of my OncePerRequestFilter class:
#Component
#Order
public class LogFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String method = request.getMethod();
String username = SecurityContextHolder.getContext().getAuthentication().getName();
String url = request.getRequestURL().toString();
// Log the info you need
// ...
filterChain.doFilter(request, response);
}
}
The only problem so far that I see with my current configuration of OncePerRequestFilter is it also includes the resources such as css / javascripts.
example these links will be also go to the filter:
http://localhost:8000/project/css/style.css
http://localhost:8000/project/3277a64fcca0dbde907d8684aed8f170.png
http://localhost:8000/project/js/script.js.map
What i want is to filter only the controller request mappings, and ignore the resources
example:
http://localhost:8000/project/accounts/client-users
http://localhost:8000/project/accounts
This code is a workaround to ignore resource file. not sure if it's the best practice tho.
#Component
#Order
public class LogFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String method = request.getMethod();
String username = SecurityContextHolder.getContext().getAuthentication().getName();
String url = request.getRequestURL().toString();
filterChain.doFilter(request, response);
}
protected boolean shouldNotFilter(HttpServletRequest request)
throws ServletException {
String url = request.getRequestURL().toString();
return isResourceUrl(url);
}
private boolean isResourceUrl(String url) {
boolean isResourceUrl = false;
List<String> resourceRequests = Arrays.asList(
"/css/", "/js/", "/scss/", "/fonts/", "/emails/",
".css", ".js", ".scss", ".eot", ".svg", ".ttf", ".woff", ".otf", ".ico", ".png");
for (String resourceRequest : resourceRequests) {
if (url.contains(resourceRequest)) {
isResourceUrl = true;
}
}
return isResourceUrl;
}
}
Use something like this:
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/example/docs",
"/swagger-resources/**",
"/swagger-ui.html");
}

Required request body is missing after making a copy using HttpServletRequestWrapper

In my project, I have a set of api calls which should filtered through certain set of common validation. In that case, I have to intercept the request before it hits the REST controller, read the request body, do the validations and pass it to the controller if the request passes the validations.
Since the HttpServletRequest cannot be deserialized more than once, I used a HttpServletRequestWrapper to make a copy of the actual request. Using the copy it makes, I do the validations.
Following is the configuration class for intercepting the requests.
public class InterceptorConfig extends WebMvcConfigurerAdapter {
#Autowired
CustomInterceptor customInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor).addPathPatterns("/signup/**");
}
}
Here is my preHandle method inside CustomInterceptor class which extends HandlerInterceptorAdaptor
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ServletRequest copiedRequest = new HttpRequestWrapper(request);
Map<String, Object> jsonMap = mapper.readValue(copiedRequest.getInputStream(), Map.class);
if(jsonMap.containsKey("userId")){
long userId = jsonMap.get("userId");
MyClass myObject= myAutowiredService.getMyObject(userId);
if(myObject == null){
response.setStatus(HttpStatus.SC_NOT_ACCEPTABLE);
return false;
}
// some more validations which end up returning false if they are met
}
return true;
}
This is my HttpRequestWrapper
public class HttpRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody;
public HttpRequestWrapper(HttpServletRequest request) throws IOException{
super(request);
try {
requestBody = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
requestBody = new byte[0];
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
public int read () throws IOException {
return byteArrayInputStream.read();
}
};
}
}
All set now. Now, when I send a request to any url with the pattern of /signup/**, all the validations are happening fine. However, once the request hits the controller method, error pops out saying the request body is not available.
Required request body is missing: public
com.mypackage.myResponseObject
com.mypackage.myController.myControllerMethod(com.mypackage.myDTO)
I am struggling to find the reason for this and also a way to overcome the issue. Is there anything I have done wrong in RequestWrapper class? or anything missing?
Help me to sort this thing out.
Thanks!
The Problem seems to be that you are using an Interceptor to read the HttpServletRequest's InputStream and just wrap it in HttpRequestWrapper but the wrapper is never returned.
I think you should use a Filter
public class CustomFilter extends OncePerRequestFilter {
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ServletRequest copiedRequest = new HttpRequestWrapper(request);
Map<String, Object> jsonMap = mapper.readValue(copiedRequest.getInputStream(), Map.class);
if(jsonMap.containsKey("userId")){
long userId = jsonMap.get("userId");
MyClass myObject= myAutowiredService.getMyObject(userId);
if(myObject == null){
response.setStatus(HttpStatus.SC_NOT_ACCEPTABLE);
//return false;
}
// some more validations which end up returning false if they are met
}
filterChain.doFilter(copiedRequest, (ServletResponse) response);
}
}
And you need to use this Filter in either web.xml or WebApplicationInitializer

Get destination controller from a HttpServletRequest

I have set up spring security to authenticate and authorize requests coming into my application. I have set up the configuration as so:
public class OAuth2ServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// ...set up token store here
resources.authenticationEntryPoint(new AuthenticationEntryPoint() {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//QUESTION
// How do I get the destination controller that this request was going to go to?
// Really, I'd like to get some information about the annotations that were on the destination controller.
response.setStatus(401);
}
});
}
I'd like to grab some information about the destination controller that this request was going to go to. The controller isn't actually going to get hit in this scenario because spring security kicked in and threw out the response before it reached the controller.
Any tips?
Thanks!
Assuming that OAuth2ServerConfiguration is a Spring managed bean, this should work for you.
...
#Autowired
private List<HandlerMapping> handlerMappings;
for (HandlerMapping handlerMapping : handlerMappings) {
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
if (handlerExecutionChain != null) {
// handlerExecutionChain.getHandler() is your handler for this request
}
}
If unable to Autowire a List of HandlerMapping, Autowire ApplicationContext and adjust as follows.
for (HandlerMapping handlerMapping : applicationContext.getBeansOfType(HandlerMapping.class).values()) {
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
if (handlerExecutionChain != null) {
// handlerExecutionChain.getHandler() is your handler for this request
}
}
You could try this:
#Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// handler is the controller
MyAnnotation annotation = ((HandlerMethod) handler).getMethod().getAnnotation(MyAnnotation.class)
// do stuff with the annotation
}
});
}
}

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