17
votes

I have recently implemented Spring Security in my Spring 4 / Hibernate Web application to handle logging in/out and different user roles.

After a lot of reading it appears to work pretty fine now, but I noticed that exceptions thrown due to a wrong Spring Security configuration were not handled gracefully using my custom handler but shown as an ugly Tomcat error page (showing HTTP Status 500 - UserDetailsService is required followed by a stacktrace).

Solving the particular error was not difficult (adding userDetailsService(userDetailsService) in the RememberMe configuration) but the fact remains that some exceptions thrown are not handled by the ControllerAdvice shown below handling MaxUploadSizeExceededException and all other runtime exceptions:

@ControllerAdvice
public class ExceptionHandlingControllerAdvice {

public static final String DEFAULT_ERROR_VIEW = "genericerror";

@ExceptionHandler(value = MaxUploadSizeExceededException.class)
public View maxUploadSizeExceededExceptionHandler(
        HttpServletRequest req) throws IOException {

    String redirectUrl = req.getRequestURL().toString();

    RedirectView rv = new RedirectView(redirectUrl);

    FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(req);
    if (outputFlashMap != null) {
        outputFlashMap.put(KeyConstants.FLASH_ERROR_KEY, "Bestand is te groot");
    }
    return rv;
}

@ExceptionHandler(value = RuntimeException.class)
public View defaultErrorHandler(HttpServletRequest req, Exception e) {

    RedirectView rv = new RedirectView("/error", true); //context relative

    StackTraceElement[] steArray = e.getStackTrace();
    StringBuilder stackTrace = new StringBuilder();
    for (StackTraceElement element: steArray) {
        stackTrace.append(element.toString() + "\n");
    }

    FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(req);
    if (outputFlashMap != null) {
        outputFlashMap.put("url", req.getRequestURL());
        outputFlashMap.put("excClassName", e.getClass().getName());
        outputFlashMap.put("excMessage", e.getMessage());
        outputFlashMap.put("excStacktrace", stackTrace.toString());
    }
    e.printStackTrace();

    return rv;
}
}

But the exception thrown by the incomplete configured Security is probably not caught by this mechanism because the login POST request is intercepted by Spring Security before any controller method is called. I would like to show ALL exceptions in graceful way on a custom error page, also the ones thrown before a Controller comes into place.

I cannot find much information about that, all error handling techniques described in the Spring manual (http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-exceptionhandlers) seems to use a Controller advice.

Is there a convenient way to handle ALL exceptions in a generic way? And makes that my Controller advice class to handle exceptions superfluous?

2
Why can't you simply change @ExceptionHandler(value = RuntimeException.class) to @ExceptionHandler(value = Exception.class). This way you should be able to handle all Exceptions.JChap
No this does not solve the issue. The problem is not that the exception class is too specific but that the exception is thrown before a Controller method is called and the ControllerAdvice comes into play.klausch
Ok Gotcha, May be then you go little low level like using the servlet exception handler.JChap

2 Answers

8
votes

As you have noticed, @ExceptionHandler does not not work for an exception that is thrown outside (lower in the heap than) Spring MVC.

You can catch all exceptions not caught elsewhere by specifying an error page in web.xml:

<error-page>
  <exception-type>java.lang.Throwable</exception-type>
  <location>/500</location>
</error-page>

You make this 500 page as a normal page, typically in Spring MVC:

@RequestMapping(value="/500")
public @ResponseBody String handleException(HttpServletRequest req) {

    // this will get the exception thrown/caught
    Throwable exception = (Throwable)req.getAttribute("javax.servlet.error.exception");

    // customize the response as you want
    return "Internal server error.";
}
3
votes

If you are using Spring Boot, you can create a custom ErrorController, there is a default one named BasicErrorController already at you hand.