404 Exception not handled in Spring ControllerAdvice - java

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);
}
}

Related

Best way to return ResponseEntity with data, empty and error in Spring Boot?

I am trying to implement a generic approach that can be used for each return types from Service to Controller in my Spring Boot app. After searching and reading several threads and articles, I see that I need an approach with #ControllerAdvice and #ExceptionHandler annotations.
For exception, I created the following class:
#ControllerAdvice
public class FileUploadException extends ResponseEntityExceptionHandler {
#SuppressWarnings("rawtypes")
#ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity handleMaxSizeException(MaxUploadSizeExceededException e) {
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED)
.body(new ResponseMessage("Too large!"));
}
}
However, I am not sure if it is ok and how I should treat Optional.empty() results in my Service and Controller. So, could you please suggest me a proper approach or give an example that is used for exception, result with data and result with Optional.empty() ?
Please note that I have the following articles, but they are too old and I am not sure if these approach are still good or there is a better approach with the new versions of Spring Boot, etc?
Guide to Spring Boot REST API Error Handling
Exception Handling in Spring MVC
Specific ExceptionHandler:
#RestController
#RequestMapping("/products")
public class ProductController {
#Autowierd private ProductService productService;
#GetMapping("/{code}")
public ResponseEntity<Product> findByCode(#PathVariable String code) {
return productService.findByCode(code).map(ResponseEntity::ok).orElseThrow(() -> NoContentException::new);
}
#ExceptionHandler(NoContentException.class)
public ResponseEntity handleNoContent(NoContentException e) {
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(new ResponseMessage("No data"));
}
}
Or Common ExceptionHandler :
#RestController
#RequestMapping("/products")
public class ProductController {
#Autowierd private ProductService productService;
#GetMapping("/{code}")
public ResponseEntity<Product> findByCode(#PathVariable String code) {
return productService.findByCode(code).map(ResponseEntity::ok).orElseThrow(() -> NoContentException::new);
}
}
Plus :
#ControllerAdvice
public class CommonExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity handleMaxSizeException(MaxUploadSizeExceededException e) {
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage("Too large!"));
}
#ExceptionHandler(NoContentException.class)
public ResponseEntity handleNoContent(NoContentException e) {
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(new ResponseMessage("No data"));
}
}

Return 404 instead of 403 when #PostAuthorize fails

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
}
}

Spring boot 2.1.4 ControllerAdvice not working [duplicate]

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);
}
}

Spring boot - taking control of 404 Not Found

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.

Spring Security Basic Authentication always causes 404

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.
}

Categories

Resources