This question already has answers here:
Spring 3.2 #ControllerAdvice Not Working
(7 answers)
Closed 3 years ago.
I can't get this exception handling work. I look similar questions/answers but none of them worked. I have this class with #ControllerAdvice to handle application level error handling. This is not a REST application. It's Spring boot & Thymeleaf
#EnableWebMvc
#ControllerAdvice
public class ErrorController {
#ExceptionHandler(LoginFailureException.class)
public ModelAndView handleLoginFailureException(LoginFailureException exception) {
log.info("Handle LoginFailureException");
return getModelAndView(exception.getMessage(), "user/login");
}
// other exception handling methods
private ModelAndView getModelAndView(String message, String viewName) {
ModelAndView mav = new ModelAndView();
FlashMessage flashMessage = new FlashMessage();
flashMessage.setType("danger");
flashMessage.setMessage(message);
mav.getModel().put("flashMessage", flashMessage);
mav.setViewName(viewName);
return mav;
}
}
In my WebSecurityConfigurerAdapter extended class, I have this
.failureHandler(loginFailureHandler)
and LoginFailureHandler class looks like this:
#Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
private MessageSource messageSource;
#Autowired
public LoginFailureHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
throw new LoginFailureException(
messageSource.getMessage("email.password.not.match", null, Locale.getDefault()));
}
}
and when I try to login with a non valid username, my app crashes and throws this exception and never reach to ExceptionHandler method.
com.company.application.controller.exception.LoginFailureException: Email or Username is not valid
at com.company.application.security.LoginFailureHandler.onAuthenticationFailure(LoginFailureHandler.java:34) ~[classes/:na]
#ControllerAdvice will only be hit when exceptions are thrown from #Controller, not #Component.
Since you are 'decorating' your login page with the danger/flash message, you might have to forward the request onto a new 'page' (e.g. user/loginerror) instead?
Try adding this to your main spring boot app to turn off auto config of Error Mvc
Configuration
#SpringBootApplication
#EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
Related
Let's say I have the following controller. (Assume that Order.customer is the customer the order belongs to and only they should be able to access it.)
#RestController
#RequestMapping("/orders")
public class OrderController {
#GetMapping
#PostAuthorize("returnObject.customer == authentication.principal")
public Order getOrderById(long id) {
/* Look up the order and return it */
}
}
After looking up the order, #PostAuthorize is used to make sure it belongs to the authenticated customer. If it is not, Spring responds with a 403 Forbidden.
Such an implementation has a problem: Clients can distinguish between orders that do not exist and orders they have no access to. Ideally, 404 should be returned in both cases.
While this could be solved by injecting the Authentication into the handler method and implementing custom logic there, is there any way to achieve this using #PostAuthorize or a similar, declarative API?
You can specify a custom AccessDeniedHandler in your Spring Security configuration.
In the following example, the handler will return a 404 Not Found on an access denied failure.
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.exceptionHandling(exceptionHandling -> exceptionHandling
.accessDeniedHandler(accessDeniedHandler())
);
}
#Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
}
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.getReasonPhrase());
}
}
You could try a ControllerAdvice to catch and transform the AccessDeniedException, which PostAuthorize throws.
#RestControllerAdvice
public class ExceptionHandlerController {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(AccessDeniedException.class)
public String handleAccessDenied(AccessDeniedException e) {
return "nothing here"; // or a proper object
}
}
I have a simple Spring MVC application in which I want to handle all the unmapped urls using #ControllerAdvice.
Here is the controller:
#ControllerAdvice
public class ExceptionHandlerController {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(NoHandlerFoundException.class)
public String handle404() {
return "exceptions/404page";
}
}
Still, every time get Whitelabel Error Page.
I tried using RuntimeException.class, HttpStatus.BAD_REQUEST and extending the class with NoHandlerFoundException but no use.
Any suggestions?
To make it work, you need to set throwExceptionIfNoHandlerFound property on DispecherServlet. You can do that with:
spring.mvc.throwExceptionIfNoHandlerFound=true
in application.properties file, otherwise the requests will always be forwarded to the default servlet and NoHandlerFoundException would ever be thrown.
The problem is, even with this configuration, it doesn't work. From the documentation:
Note that if
org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler is
used, then requests will always be forwarded to the default servlet
and NoHandlerFoundException would never be thrown in that case.
Because Spring Boot uses by default the org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler you'll have to override this using your own WebMvcConfigurer:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// Do nothing instead of configurer.enable();
}
}
Of course, the above class might be more complicated in your case.
Another way to do so is ErrorController
#Controller
public class MyErrorController implements ErrorController {
#GetMapping("/error")
public ModelAndView errorHandler(HttpServletRequest req) {
// Get status code to determine which view should be returned
Object statusCode = req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
// In this case, status code will be shown in a view
ModelAndView mav = new ModelAndView("error_default");
mav.addObject("code", statusCode.toString());
return mav;
}
public String getErrorPath() {
return "/error";
}
}
Add the below line in application.properties
spring.mvc.throwExceptionIfNoHandlerFound=true
and #EnableWebMvc with #ControllerAdvice,
Note: add #Override over handleNoHandlerFoundException method
this worked for me!
#EnableWebMvc
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
CustomResponse response = new CustomResponse();
response.setTimestamp(LocalDateTime.now());
response.setMessage(ApplicationErrorCodes.NO_HANDLER_FOUND.getErrorMessage());
response.setStatus(HttpStatus.BAD_REQUEST);
response.setErrorCode(ApplicationErrorCodes.NO_HANDLER_FOUND.getErrorCode());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
}
I created my own exception and trying to handle it with spring MVC.
Spring Handling all exceptions as it should except my own exceptions(User Defined). What is the problem, how to handle User Defined Exceptions in Spring MVC?
I used #ExceptionHandler in my controller and in #ControllerAdvice, used SimpleMappingExceptionResolver bean in Dispatcher servlet no help. In every stage all the System defined exceptions are handled as expected.
User Defined Exception:
public class GeneralUserException extends Exception {
private static final long serialVersionUID = 1L;
String message;
public GeneralUserException (String message){
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Exception Handler in controller:
#ExceptionHandler(value = GeneralUserException.class)
public String defaultErrorHandler(HttpServletRequest req, GeneralUserException e) {
return "general_error";
}
Exception Handler in #ControllerAdvice:
#ControllerAdvice
class GlobalDefaultExceptionHandler {
#ExceptionHandler(value = GeneralUserException.class)
public String defaultErrorHandler(HttpServletRequest req, GeneralUserException e) {
return "general_error";
}
}
Bean in dispatcher Servlet Context (WebAppConfig):
public class WebAppConfig extends WebMvcConfigurerAdapter{
#Bean
public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("GeneralUserException", "general_error");
r.setExceptionMappings(mappings);
r.setDefaultErrorView("error");
return r;
}
...
}
All these are not showing error page. But when I change GeneralUserException to Exception/RuntimeException it works. I tried to put full qualified name too.
I use spring, spring MVC, spring Security if it's helpful. And I don't use Spring Aspects.
Note: SimpleMappingExceptionResolver is returning "error" page on error(so its working) for DefaultErrorView. When I use RuntimeException instead GeneralUserException it returns "general_error" view so its working for RuntimeException.
I'm trying to figure out the simplest way to take control over the 404 Not Found handler of a basic Spring Boot RESTful service such as the example provided by Spring:
https://spring.io/guides/gs/rest-service/
Rather than have it return the default Json output:
{
"timestamp":1432047177086,
"status":404,
"error":"Not Found",
"exception":"org.springframework.web.servlet.NoHandlerFoundException",
"message":"No handler found for GET /aaa, ..."
}
I'd like to provide my own Json output.
By taking control of the DispatcherServlet and using DispatcherServlet#setThrowExceptionIfNoHandlerFound(true), I was able to make it throw an exception in case of a 404 but I can't handle that exception through a #ExceptionHandler, like I would for a MissingServletRequestParameterException. Any idea why?
Or is there a better approach than having a NoHandlerFoundException thrown and handled?
It works perfectly Fine.
When you are using SpringBoot, it does not handle (404 Not Found) explicitly; it uses WebMvc error response. If your Spring Boot should handle that exception, then you should do some hack around Spring Boot. For 404, the exception class is NoHandlerFoundException; if you want to handle that exception in your #RestControllerAdvice class, you must add #EnableWebMvc annotation in your Application class and set setThrowExceptionIfNoHandlerFound(true); in DispatcherServlet. Please refer to the following code:
#SpringBootApplication
#EnableWebMvc
public class Application {
#Autowired
private DispatcherServlet servlet;
public static void main(String[] args) throws FileNotFoundException, IOException {
SpringApplication.run(Application.class, args);
}
#Bean
public CommandLineRunner getCommandLineRunner(ApplicationContext context) {
servlet.setThrowExceptionIfNoHandlerFound(true);
return args -> {};
}
}
After this you can handle NoHandlerException in your #RestControllerAdvice class
#RestControllerAdvice
public class AppException {
#ExceptionHandler(value={NoHandlerFoundException.class})
#ResponseStatus(code=HttpStatus.BAD_REQUEST)
public ApiError badRequest(Exception e, HttpServletRequest request, HttpServletResponse response) {
e.printStackTrace();
return new ApiError(400, HttpStatus.BAD_REQUEST.getReasonPhrase());
}
}
I have created ApiError class to return customized error response
public class ApiError {
private int code;
private String message;
public ApiError(int code, String message) {
this.code = code;
this.message = message;
}
public ApiError() {
}
//getter & setter methods...
}
According to the Spring documentation appendix A. there is a boolean property called spring.mvc.throw-exception-if-no-handler-found which can be used to enable throwing NoHandlerFoundException. Then you can create exception handler like any other.
#RestControllerAdvice
class MyExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(MyExceptionHandler.class);
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(NoHandlerFoundException.class)
public String handleNoHandlerFoundException(NoHandlerFoundException ex) {
log.error("404 situation detected.",ex);
return "Specified path not found on this server";
}
}
#ExceptionHandler itself without #ControllerAdvice (or #RestControllerAdvice) can't be used, because it's bound to its controller only.
In short, the NoHandlerFoundException is thrown from the Container, not from your application within your container. Therefore your Container has no way of knowing about your #ExceptionHandler as that is a Spring feature, not anything from the container.
What you want is a HandlerExceptionResolver. I had the very same issue as you, have a look at my solution over there: How to intercept "global" 404s on embedded Tomcats in spring-boot
The #EnableWebMvc based solution can work, but it might break Spring boot auto configurations.
The solution I am using is to implement ErrorController:
#RestController
#RequiredArgsConstructor
public class MyErrorController implements ErrorController {
private static final String ERROR_PATH = "/error";
#NonNull
private final ErrorAttributes errorAttributes;
#RequestMapping(value = ERROR_PATH)
Map<String, Object> handleError(WebRequest request) {
return errorAttributes.getErrorAttributes(request, false);
}
#Override
public String getErrorPath() {
return ERROR_PATH;
}
}
The solution for this problem is:
Configure DispatcherServlet to throw and exception if it doesn't find any handlers.
Provide your implementation for the exception that will be thrown from DispatcherServlet, for this case is the NoHandlerFoundException.
Thus, in order to configure DispatcherServlet you may use properties file or Java code.
Example for properties.yaml,
spring:
mvc:
throw-exception-if-no-handler-found: true
Example for properties.properties,
spring.mvn.throw-exception-if-no-handler-found=true
Example for Java code, we just want to run the command servlet.setThrowExceptionIfNoHandlerFound(true); on startup, I use the InitializingBean interface, you may use another way. I found a very well written guide to run logic on startup in spring from baeldung.
#Component
public class WebConfig implements InitializingBean {
#Autowired
private DispatcherServlet servlet;
#Override
public void afterPropertiesSet() throws Exception {
servlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Be careful! Adding #EnableWebMvc disables autoconfiguration in Spring Boot 2, meaning that if you use the annotation #EnableWebMvc then you should use the Java code example, because the spring.mvc.* properties will not have any effect.
After configuring the DispatcherServlet, you should override the ResponseEntityExceptionHandler which is called when an Exception is thrown. We want to override the action when the NoHandlerFoundException is thrown, like the following example.
#ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String responseBody = "{\"errormessage\":\"WHATEVER YOU LIKE\"}";
headers.add("Content-Type", "application/json;charset=utf-8");
return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_FOUND, request);
}
}
Finally, adding a break point to method handleException of ResponseEntityExceptionHandler might be helpful for debugging.
I've been building a REST API using Spring. I'm using Basic Authentication with Spring Security (3.2) and I'm having an issue where any unauthenticated request causes a 404 error, even with an implementation of AuthenticationEntryPoint (regardless, Spring should give a 401 as far as I am aware by default). Requesting the resource in my browser, I am not even prompted for credentials. Here's a screenshot of the problem:
After reading the documentation and a number of tutorials on the subject, I can't seem to find where I've gone wrong. The only thing I can imagine is happening is some exception that's being caught.
Spring Security configuration:
#Slf4j
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String REALM_NAME = "Autopulse API";
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// Set to stateless authentication.
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.csrf().disable();
httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
httpSecurity.userDetailsService(userDetailsService);
httpSecurity.authorizeRequests()
.antMatchers("/make/private").authenticated();
httpSecurity.httpBasic().realmName(REALM_NAME);
}
}
Authentication Entry Point:
#Slf4j
#Component
public class HttpBasicAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
Controller:
#Slf4j
#RestController
#RequestMapping("/make")
public class MakeController {
#RequestMapping(value = "/private", method = RequestMethod.GET)
public String getPrivateStuff() {
return "private things!";
}
}
When I provide valid user credentials in the Authorization header, I can see the protected resource ("private things!"), however if I do not provide an Authorization header, or I enter invalid credentials, I simply get the 404 error. I can attach my user details service and user details classes if required.
I figured it out. The problem came down to Spring and exception handling. I had a class called ExceptionController that looked like:
#Slf4j
#RestController
#ControllerAdvice
public class ExceptionController implements ErrorController {
// #ExceptionHandler methods here.
#Override
public String getErrorPath() {
return null;
}
}
It turns out that, by implementing ErrorController, I was handing control to this class AFTER the authentication entry point where Spring could not find the appropriate method and would eventually throw the unwanted 404 error. The fix was to remove those details:
#Slf4j
#RestController
#ControllerAdvice
public class ExceptionController {
// #ExceptionHandler methods here.
}