Spring Boot - Remove external "/error" endpoint access - java

Introduction
I have a custom ErrorController implementation to handle all exceptions and create a custom error message:
#RestController
public class CustomErrorController implements ErrorController {
#RequestMapping("/error")
public ResponseEntity handleError(HttpServletRequest request) {
HttpStatus status = HttpStatus.valueOf((Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
String body = ... // Code to calculate the body based on the request
return ResponseEntity.status(status.value()).body(body);
}
#Override
public String getErrorPath() {
return "/error";
}
}
Problem Description
However, this also enables access to the path /error which I would like to disable.
When trying to access https://localhost:8443/error, a NullPointerException is thrown by the HttpStatus.valueOf() method, because the status code could not be extracted. As a result, an Internal Server Error (500) is created, which is run through my custom controller, creating a custom 500 error response.
Temporary Fix
As a workaround, I can check if the status code attribute exists, and handle that case separately. But it is a work-around and not an actual fix.
The Question
What I would like is to disable the /error mapping from external access. If attempted, the result should be Not Found (404) which is then run through my custom controller.
Is the #RequestMapping("/error") necessary or could this be implemented differently?
Edits
Spring Boot version is 2.1.2.RELEASE
The server.error.whitelabel.enabled property is set to false. The issue does not seem to be related with it.

Related

Content-Type is missing with Spring GET/POST method

I am new to Spring and I am trying to do the basic GET and POST method.
This is how I am trying to do the methods:
#RestController
public class DeskController {
#Autowired
private DeskDao dao;
#GetMapping("desks")
public List<Desk> getDesks() {
System.out.println(dao.findById(1L));
return dao.findAll();
}
#PostMapping("desks")
public Desk save(#RequestBody #Valid Desk desk) {
Desk deskObj = dao.save(desk);
System.out.println(deskObj);
return deskObj;
}
When I am calling the POST method like this I get the pring with the actual object that I had called it with so it is working fine, but I also get this error:
javax.ws.rs.ProcessingException: Content-Type is missing
And when trying to call GET it tells me that:
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
I am aware that I have not included the whole code, but I will add what is needed to resolve this error since there are a lot of classes.
My questions are, what do I do against the first error and why is GET method not supported?
Two things you need to change :
Use a / to indicate that for this path you will perform an
operation. For eg : (/desks)
Use the annotation #Consumes to
indicate that this method accepts the payload in particular format. For eg : #Consumes(MediaType.APPLICATION_JSON) annotated over your save() method.

Why doesn't my spring boot error controller work?

I have a simple spring boot web application (running under Tomcat) and I cannot figure out how to get an error handler to work. So far I've created the web application and controllers and they all work fine. That is, they display Thymeleaf templates. Upon error. I get the default white label error page.
I first disabled this white label error page by adding an EnableAutoConfiguration annotation, thus:
#SpringBootApplication(scanBasePackages="au.com.sample")
#EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})
public class SampleWebApp extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SampleWebApp.class);
}
}
Now, if I browse to an unknown page I get a generic 404 HTML error page containing:
<html>...<body><h1>HTTP Status 404 – Not Found</h1></body></html>
So that step worked. Now when I try and add my own error controller it does not trigger when I browse to an end point that does not exist:
#Controller
#RequestMapping("/")
public class ErrorHandlingController implements ErrorController {
#Override
public String getErrorPath() {
return "/error";
}
#RequestMapping(/"error")
public ModelAndView handleError() {
ModelAndView mav = new ModelAndView();
mav.addObject("title", "Error");
mav.setViewName("layout");
return mav;
}
}
"layout" is my existing thymeleaf template that works fine for my other end points.
I've tried several variations of the error controller, eg. changing the endpoint to "error" (not "/error"), using #RestController instead of #Controller but with no luck. In every case, I get the same generic 404 HTML instead of my hand-crafted template.
Can anyone see what I'm doing wrong or have any tips on how to track down my problem.
To show a custom error page for a specific status is pretty easy in Spring Boot. Just add an <error-code>.html into the src/main/resources/templates/errors directory to have it resolved. Or add a generic error.html as a fallback. See the reference guide for this feature (see also this).
If you want to add your own error handling just add a bean of type ErrorController or if you want to only add attributes add an ErrorAttributes typed bean to your configuration. See this section for more information.
A late answer to the original question of why Spring doesn't call your error controller: I think you are excluding the magic that makes the custom ErrorController work. In a Spring-Boot v2.1.6 app I had to allow (NOT exclude) the ErrorMvcAutoConfiguration class, then it used a custom MyController-implements-ErrorController class on page-not-found situations etc. It seems to be an either-or situation. I think you should only exclude the ErrorMvc.. class if you are using Thymeleaf templates AND you have published template files in a templates/ directory, because in that case you don't really need a custom error controller. HTH.

How to test only specific http request method available for controller method in Spring MVC?

I need to check if only specific http method is available for some url.
For example, if there is a controller like this
#Controller
public class FooController {
#RequestMapping(value = "bar", method = RequestMethod.GET)
public void bar() {/*do something*/};
...
}
For controller test I use junit(4.10), spring-test(3.2.10) and easymock(3.1).
If I write test like this
#Test
public void testBar() throws Exception {
mockMvc.perform(post("bar").session(session))
.andExpect(/*some application's default error response*/);
}
it will pass (although test calls post-method, not get-method).
So I'm looking for a proper way to make sure, that my rest resources are only avaiable by request methods specified in documentation. Two solutions came to my mind:
write tests with wrong request methods and somehow check resource is not available
add custom exception resolver to process org.springframework.web.HttpRequestMethodNotSupportedException: Request method '__' not supported and return same application default error response, but with https status 405 Method not allowed.
What would you suggest and how to check in controller test request method?
Thanks in advance.
You would need to check the status of all the request methods, you could do it using andExpect with status().isMethodNotAllowed() or status().isNotFound() depends on your needs:
Examples:
get: mockMvc.perform(get("bar").andExpect(status().isNotFound()) or mockMvc.perform(get("bar").andExpect(status().isMethodNotAllowed())
Do the same same for put, delete, ....

Spring Security - Ajax calls ignoring #ExceptionHandler

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.

Spring Boot - Error Controller to handle either JSON or HTML

I have a spring boot application.
I have a custom error controller, that is mapped to using ErrorPage mappings. The mappings are largely based on HTTP Status codes, and normally just render a HTML view appropriately.
For example, my mapping:
#Configuration
class ErrorConfiguration implements EmbeddedServletContainerCustomizer {
#Override public void customize( ConfigurableEmbeddedServletContainer container ) {
container.addErrorPages( new ErrorPage( HttpStatus.NOT_FOUND, "/error/404.html" ) )
}
And my error controller:
#Controller
#RequestMapping
public class ErrorController {
#RequestMapping( value = "/error/404.html" )
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public String pageNotFound( HttpServletRequest request ) {
"errors/404"
}
This works fine - If I just enter a random non-existent URL then it renders the 404 page.
Now, I want a section of my site, lets say /api/.. that is dedicated to my JSON api to serve the errors as JSON, so if I enter a random non-existent URL under /api/.. then it returns 404 JSON response.
Is there any standard/best way to do this? One idea I tried out was to have a #ControllerAdvice that specifically caught a class of custom API exceptions I had defined and returned JSON, and in my standard ErrorController checking the URL and throwing an apprpriate API exception if under that API URL space (but that didn't work, as the ExceptionHandler method could not be invoked because it was a different return type from the original controller method).
Is this something that has been solved?
The problem was my own fault. I was trying to work out why my #ExceptionHandler was not able to catch my exception and return JSON - As I suggested at the end of my question, I thought I was having problems because of conflicting return types - this was incorrect.
The error I was getting trying to have my exception handler return JSON was along the lines of:
"exception": "org.springframework.web.HttpMediaTypeNotAcceptableException",
"message": "Could not find acceptable representation"
I did some more digging/experimenting to try to narrow down the problem (thinking that the issue was because I was in the Spring error handling flow and in an ErrorController that was causing the problem), however the problem was just because of the content negotiation stuff Spring does.
Because my errorPage mapping in the web.xml was mapping to /error/404.html, Spring was using the suffix to resolve the appropriate view - so it then failed when I tried to return json.
I have been able to resolve the issue by changing my web.xml to /error/404 or by turning off the content negotiation suffix option.
Now, I want a section of my site, lets say /api/.. that is dedicated
to my JSON api to serve the errors as JSON, so if I enter a random
non-existent URL under /api/.. then it returns 404 JSON response.
Is there any standard/best way to do this? One idea I tried out was to
have a #ControllerAdvice that specifically caught a class of custom
API exceptions I had defined and returned JSON, and in my standard
ErrorController checking the URL and throwing an apprpriate API
exception if under that API URL space (but that didn't work, as the
ExceptionHandler method could not be invoked because it was a
different return type from the original controller method).
I think you need to rethink what you are trying to do here. According to HTTP response codes here
The 404 or Not Found error message is an HTTP standard response code
indicating that the client was able to communicate with a given
server, but the server could not find what was requested.
So when typing a random URL you may not want to throw 404 all the time. If you are trying to handle a bad request you can do something like this
#ControllerAdvice
public class GlobalExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ErrorResponse> noRequestHandlerFoundExceptionHandler(NoHandlerFoundException e) {
log.debug("noRequestHandlerFound: stacktrace={}", ExceptionUtils.getStackTrace(e));
String errorCode = "400 - Bad Request";
String errorMsg = "Requested URL doesn't exist";
return new ResponseEntity<>(new ErrorResponse(errorCode, errorMsg), HttpStatus.BAD_REQUEST);
}
}
Construct ResponseEntity that suites your need.

Categories

Resources