I'm using #ControllerAdvice to implement a global exception handler but I got some issues with the use of HttpServletResponse#sendError() method.
#ExceptionHandler can catch all kinds of exception, but not HttpServletResponse#sendError() invocations. I understand that HttpServletResponse#sendError() is not an exception, but I need to process it, and then redirect to a generic error page.
I'm using Spring Security for authentication, and in the failed handler, I set status 401 to the response:
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
String contentType = request.getContentType();
logger.info(contentType);
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
}
Then in the #ControllerAdvice, I tried to use #ExceptionHandler and #ResponseStatus to catch 401 but it does not work:
#ResponseStatus (value=HttpStatus.UNAUTHORIZED, reason="You don't have access right on this page")//401
#ResponseBody
#ExceptionHandler(DataIntegrityViolationException.class)
public String handleHttpStatus(DataIntegrityViolationException e){
return "genericerror";
}
Can #ExceptionHandler methods process HttpServletResponse#sendError() invocations?
Spring Security is a separate framework from Spring MVC. Spring Security is a Servlet filter (have a look in your web.xml) which intercepts requests before they reach Spring MVC. You cannot process exceptions that happen at the Spring Security level in #ControllerAdvice (as #ControllerAdvice is part of Spring MVC).
As you said it yourself, HttpServletResponse#sendError() does not throw an exception. According to this document, it sends an error response to the client using the specified status code and clears the buffer.
In your web.xml file you can define static web pages (1) for error response codes and (2) for exceptions. For example:
<error-page>
<error-code>401</error-code>
<location>/errors/genericerror</location>
</error-page>
Related
I have custom security filter which serves as additional authorization step.
The filter checks if a user can be authorized and throws an exception if the user is not supposed to access the resource.
The problem is that if I throw an exception from the filter - it doesn't get mapped to correct status code (in my case I need HTTP 403).
I can't use #ControllerAdvice and #ExceptionHandler because security filters work before controller handling.
I thought may be I'm doing it wrong and I shouldn't throw an exception, or I should throw a very specific one.
Q: Is there any way to automatically map exceptions from filters to proper status codes? Or is there a way to implement the filter without exceptions?
Note: I also read this post, but from debug I see that my filter chain doesn't contain ExceptionTranslationFilter.
Or is there a way to implement the filter without exceptions?
Obviously, you can directly write to response and return from the very point you catch an authentication or authorization failure. Throwing exceptions then globally handling it seemed too much unnecessary overwork for me since I had only one JWT authentication filter implementing - AbstractAuthenticationProcessingFilter
Whenever an authentication condition is not met , like JWT token parsing issue, missing token , malformed token etc , I simply log error , set HttpStatus and return null from attemptAuthentication method.
This way my whole logic is encapsulated in single class. I also had to send 401 for all cases but error messages were different and that was handled by a simple utility method.
I don't find any fault in throwing and then handling exceptions if you have to do that from many places in your application and in that case handler could be specified as in Nicholas Smith's answer.
The Spring recommended way is to have a #Component that implements AccessDeniedHandler and then in your class that extends WebSecurityConfigurerAdapter register it to the .accessDeniedHandler() in the configure(HttpSecurity ...) method. That's for 403, if you want 401 errors to be wrapped as well then you need to extend Http401AuthenticationEntryPoint. I'll focus on 403 cases below.
Your WebSecurityConfigurerAdapter
#Override
protected void configure(final HttpSecurity http) throws Exception {
...
http
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(<YOUR Http401AuthenticationEntryPoint>)
.and()
.exceptionHandling().accessDeniedHandler(<YOUR ACCESS DENIED HANDLER>);
...
}
Your AccessDeniedHandler
#Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setHeader("Content-Type", "application/hal+json;charset=UTF-8");
response.getOutputStream()
.print(objectMapper
.writeValueAsString("Access Denied");
response.setStatus(403);
}
i want to log all server side errors. so i added
#ControllerAdvice
public class MyHandler {
#ExceptionHandler(Throwable.class)
ResponseEntity handle(HttpServletRequest req, Throwable t) {
// if t is clientError return 400
// else log and return 500
}
}
but now i have to manually list all default spring's 4xx errors like e.g. HttpMediaTypeNotSupportedException. what can i do to first let spring's default handler handles all errors it can, and only then run my MyHandler?
or maybe there is some other way of intercepting unhandled errors in spring?
I have a controller in my project that handles all exceptions defined like this:
#ControllerAdvice
public class GlobalExceptionHandlingController {
#ResponseBody
#ExceptionHandler(value = AccessDeniedException.class)
public ResponseEntity accessDeniedException() {
Logger.getLogger("#").log(Level.SEVERE, "Exception caught!");
return new ResponseEntity("Access is denied", HttpStatus.FORBIDDEN);
}
}
I'm focusing on one specific exception here and that is AccessDeniedException that is thrown by Spring Security on unauthorized requests. This is working properly for "normal" aka non-ajax requests. I can click on a link or enter URL directly in the location bar and I will see this message if request is unauthorized.
However on AJAX request (using Angular for it) I'm getting standard 403 error page as a response but what's interesting is that I can see that AccessDeniedException is caught by this controller!
I did some research and it seems that I need to have custom AccessDeniedHandler so I made this:
Added this lines in my Spring Security configuration:
.and()
.exceptionHandling().accessDeniedPage("/error/403/");
and I made special controller just to handle this:
#Controller
public class AjaxErrorController {
#ResponseBody
#RequestMapping(value = "/error/403/", method = RequestMethod.GET)
public ResponseEntity accessDeniedException() {
return new ResponseEntity("Access is denied (AJAX)", HttpStatus.FORBIDDEN);
}
}
Now this is working fine but the exception is still caught in the first controller but return value of that method is getting ignored. Why?
Is this how it's supposed to be done? I have a feeling that I am missing something here.
I'm using Spring 4.2.5 with Spring Security 4.0.4.
Although I don't know all the details, my theory is that it can be a content type issue.
Often when doing AJAX requests, the response is expected to be in JSON, so the browser will add an Accept: application/json header to the request.
Your response entity on the other hand:
new ResponseEntity("Access is denied", HttpStatus.FORBIDDEN)
is a text response, the default Content-Type of this with a typical Spring setup is text/plain.
When Spring detects that it can't deliver a response with type the client wants, it fallbacks to the default error page.
Continuing the thread: Global exception page in Apache Tiles and Spring MVC
I have an error page defined in my web.xml:
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/404.jsp</location>
</error-page>
I have noticed one more issue in Spring MVC:
a)
if no #RequestMapping is matched then indeed, my custom error jsp is printed.
b)
if a #RequestMapping is matched, but the method sets an error status eg.
response.setStatus(404);
then Tomcat's (7.0.29) default error page is chosen, not my jsp.
Why? How to make my 404 page be displayed always ?
I think what you're experiencing is caused by the line you mentioned: response.setStatus(404);
This method doesn't trigger the container's error page mechanism, it should be used when there is no error. To trigger the mechanism, you have to use sendError, which is recommended in the official docs.
BTW I've just found out that the behavior differs between Servlet Spec. 2.3 and 2.4
(read here). In 2.3 the two methods are said to do the very same thing, whereas in 2.4 they differ..............
With spring MVC is preferable using build-in exception handler to show error page to the users.
Take a look this tutorial: http://doanduyhai.wordpress.com/2012/05/06/spring-mvc-part-v-exception-handling/
You may want to take a look at ExceptionHandler.
It is really nice and flexible and allows you to implement logic to display different error pages and output different HTTP reponse codes depending on the exception (this is not always a requirement but is nice to know you could do that easily).
I paste here my code as I think it can be useful to solve common issues regarding this topic.
#ExceptionHandler(Exception.class)
public ModelAndView resolveException(Exception ex,
HttpServletRequest request,
HttpServletResponse response) {
// I get an email if something goes wrong so that I can react.
if (enableEmailErrorReporting)
sendExceptionEmail(request.getRequestURL().toString(), ex);
ModelAndView mav = getModelAndView(ex, request);
setStatusCode(ex, response);
return mav;
}
protected ModelAndView getModelAndView(Exception ex,
HttpServletRequest request) {
// Here you can implement custom logic to retrieve the correct
// error page depending on the exception. You should extract
// error page paths as properties or costants.
return new ModelAndView("/WEB-INF/app/error.html");
}
// This is really nice.
// Exceptions can have status codes with the [`ResponseStatus`][2] annotation.
private void setStatusCode(Exception ex, HttpServletResponse response) {
HttpStatus statusCode = HttpStatus.BAD_REQUEST;
ResponseStatus responseStatus =
AnnotationUtils.findAnnotation(ex.getClass(),
ResponseStatus.class);
if (responseStatus != null)
statusCode = responseStatus.value();
response.setStatus(statusCode.value());
}
The logic here is that a controller method throws an uncatched exception. Spring will invoke the method marked with ExceptionHandler (you can have one per controller, per exception, or a global default one, usually I make all my controllers inherit from a BaseController class where I define this method). Passed to the method are the exception itself and any other info you need to choose the right view to display. Even more, you can see if on the exception has been declared a specific HTTP response code (eg, 500 for unchecked exception, 400 for validation errors, etc...) and return that code along with your error page.
#Controller
public class CentralizedExceptionController extends DefaultHandlerExceptionResolver {
#Override
protected ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("working?!");
return new ModelAndView();
}
I have this in my code, but in case of a 404 its never called.
(I dont have an error-page defined in my web.xml, and i dont want to)
Take a look at this jira issue: https://jira.springsource.org/browse/SPR-8837?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=72648#comment-72648
If your Spring dispatcher servlet is configured to process all/most URLs, then you are probably getting the 404 error along with this DispatcherServlet log message from console:
No mapping found for HTTP request with URI [xxx]
This indicates that Spring's DispatcherServlet is processing the request but do not have an appropriate #RequestMapping to dispatch to.
A simple solution would be to limit requests processed by dispatcher servlet by reconfiguring web.xml's servlet-mapping > url-pattern to only URLs specified by your application's #RequestMappings. However, this is NOT very practical (so don't do this).
One way to overcome this would be to create a #RequestMapping that handles all "unhandled" request mappings - some kind of fallback request mapping.
#RequestMapping("**")
#ResponseBody
public String fallbackRequestMapping() {
return "do something useful...";
}
Note that this answer is similar in approach to Dani's answer but written with annotation based development in mind. Therefore, it is useful to understand the associated Spring issue.
plz check. Your controller class name should not be Controller.java.
You need to use #ExceptionHandler annotation to your method:
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
public ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
}