Empty #PathVariable results in ArgumentMismatchException - java

I have an endpoint in my controller configured as such:
#RequestMapping(value = "/users/{userId}/data", method = RequestMethod.GET)
public void getUserData(#PathVariable("userId") #Valid #NotNull Integer userId, HttpServletRequest request) {
}
If the client sends request to this endpoint with blank userId, I'm assuming Spring is interpreting the URL as /users//data because this exception is thrown:
2017-03-03 11:13:41,259 [[ACTIVE] ExecuteThread: '3' for queue:
'weblogic.kernel.Default (self-tuning)'] ERROR class
com.xxxx.web.controller.custom.ExceptionHandlingController:
RuntimeException thrown:
org.springframework.web.method.annotation.MethodArgumentTypeMismatchException:
Failed to convert value of type 'java.lang.String' to required type
'java.lang.Integer'; nested exception is
java.lang.NumberFormatException: For input string: "data"
What is the best way to handle this use case? I'm wary about casting userId as a String and catching the exception, that seems like a hack. I do not want to rely on the client always sending a proper request either. I am currently using Spring-Boot v1.2.6.RELEASE and am willing to upgrade versions if I know that will fix it.

You can create a class to handle global exceptions and annotate it with #ControllerAdvice.
#ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandle {
#ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<Object> handleMethodArgumentTypeMismatch(
MethodArgumentTypeMismatchException ex, WebRequest request) {
//Handle your exception here...
}
}
Here is a good write up on how to a lot of things you can do with #ControllerAdivce
http://www.baeldung.com/global-error-handler-in-a-spring-rest-api

You have a conflict in your request mapping.
Most likely you also have GET mapping for /users/{userId}. This is the mapping which was applied instead of the one from your question.
The issue is that you request for /users//data, web server automatically replaces double slash by single slash. The result request exactly matches to this pattern /users/{userId} but not to the one you posted. Finally spring can't cast data as an integer.
If you remove /users/{userId} (just for test reasons) you'll probably get 404 error code requesting the same url.
EDIT:
In fact you should not care that someone sends wrong requests to your REST API. REST API is a contract and both sides should follow this contract. You as a backend point should only handle a request and provide appropriate error code and good description in case if the request is wrong. data was never a correct user-id make sure this information is included into the response instead of that technical stuff.
One of the possible solutions is to validate ids using pattern.
In your case it will look like this:
#RequestMapping(value = "/users/{userId:\\d+}/data", method = GET)
#RequestMapping(value = "/users/{userId:\\d+}", method = GET)
In this case spring will automatically filter non-number ids and provide HTTP 404 for them.

Related

Spring Boot how to return custom error message when validating #PathVariable parameters

Is it possible to add some custom validation message to path variable?
I have some GET
#GetMapping("/v2/tw/{id}")
public TwDto getTw(Authentication auth, #PathVariable Long id) {
In case of /v2/tw/someString I'd like to catch error and add some custom error message like "invalid tw ID"... How to do that? In ControllerAdvice add some ExceptionHandler?
For your particular use case, you can use #ExceptionHandler in the Controller or in a #ControllerAdvice class as shown here. For example, I am returning NOT_FOUND error for the sake of it.
#ExceptionHandler({MethodArgumentTypeMismatchException.class})
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "this is the reason")
public void handle() {
}
You may not see the reason in the actual error response, until you enable
server:
error:
include-message: always
If you think your #ExceptionHandler is only needed in a Controller class you can keep the method inside the controller. Alternatively you can create a #ControllerAdvice class and put the method there, so that you can reuse across multiple controllers in your application.
However, if you want a more complex validation, I will suggest to keep the id type to String and then cast manually into Long and perform the validation. Doing so you can throw your own RuntimeException and handle different cases.

Spring boot REST server throws HttpRequestMethodNotSupportedException on POST of unexpected request body with #Validated

Does Spring throw HttpRequestMethodNotSupportedException when a request body is not valid and #Valid (or #Validated) is used? I really expected MethodArgumentNotValidException.
Details: I have a small REST server built on Spring-Boot version 2.2.4. One of the methods looks like this:
#PostMapping("/yapp")
public Yapp kickYapp(#Validated #RequestBody YappDescriptor yappDescriptor) {
logger.debug("kickYapp descriptor {}", yappDescriptor);
doWork(yappDescriptor);
}
The YappDescriptor has annotations like "required" but nothing for valid values, ranges, etc. When I POST a well-formed JSON object with values for all the required fields as defined in the YappDescriptor POJO, the Spring controller method is found and invoked as expected.
I tried a couple error scenarios:
1) If I POST a well-formed JSON object that has only null values for the expected fields, the method is found and entered as expected.
2) If I POST a well-formed JSON object with a key that does not match any of the POJO's fields, the method is NOT found and entered. In watching class org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler I see the exception is HttpRequestMethodNotSupportedException and the server answers 405 "Request method 'POST' not supported".
In this controller class, kickYapp is the only POST-mapped method at the specified path, so I think that answer is pretty confusing. Altho I'm definitely sending a bad request (unexpected data), I am surprised my POST-mapped method is not found and invoked.
This post Validating if request body in HTTP POST request is null in Spring Boot controller suggests I should be seeing HttpMessageNotReadableException which would be helpful, but I never get that exception.
Many other questions on SO seem to be about enabling validation of request bodies, like Spring 4.1.7 validate request body , but I seem to be past that.
Thanks in advance for helping me understand this behavior and maybe improve the server to help my users discover their errors more easily (which saves me time :). Thought I could maybe extend that method to accept a BindingResult parameter and report errors that way, but that's a non-starter if the controller method is never entered.
Update to respond to comments: yes I could have used #Valid. In my tests annotation #javax.validation.Valid and #org.springframework.validation.annotation.Validated have the same effect, both turned on validation of the RequestBody parameter.
why not use #Valid?
like so:
public ResponseEntity<SalaryDto> update(#Valid #RequestBody SalaryDto subject)
and don't forget to use javax.validation validation annotations in your request body object

Spring boot endpoint parameter signature, is throwing ambiguous endpoint exception when both parameters are given

Due to the way it was engineered in the past, there was one endpoint created for a specific parameter called foo in this case.
However the requirement meant that the endpoint could be used either with foo or a new parameter called bobby.
After trying to consolidate into one endpoint, the refactor work was too much.
So I opted for overloading the endpoint and using the spring boot trick to have the signature dictated by the request params.
Like so:
#GetMapping(params = {"foo"})
public CollectionResource<FooResource> get(#RequestParam("foo") String foo, ...) {} ...
#GetMapping(params = {"bobby"})
public CollectionResource<FooResource> get(#RequestParam("bobby") {} ...
This works well when interacting with the endpoints like so:
localhost:8080/testEndpoint?foo=bar
localhost:8080/testEndpoint?bobby=tables
However I discovered an edge case when trying the following:
localhost:8080/testEndpoint?bobby=tables&foo=bar
Which throws the following runtime exception
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/testEndpoint/':
This endpoint is not hit by users but programmatically, so its very low chance this case would happen. However is there a way to setup the controller so it can handle this and just throw a BadRequest etc. instead of blowing up?
Spring Boot Version : 1.5.16.RELEASE
Why not choose a primary endpoint?
For the first, just add the additional parameter to it
public CollectionResource<FooResource> get(#RequestParam("foo") String foo, ...
,#RequestParam("bobby")) {
By that first endpoint will be chosen in this corner case
Spring can not distinguish the endpoints on the basis of Request param.
Instead of two endpoint for serving two Request Param, have only one end point with two Request params. You have the options of making it not required.
#RequestParam("foo") String foo required = false, #RequestParam("bobby") String foo required = false
This gives you more simpler way to handle the API
try with exception handlers
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorResponse error = new ErrorResponse("Server Error", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}

XSS attacks: change exception thrown in a Spring Boot CRUDRepository ?

I'm developing an API for an application that is able to find so-called ProductSolutions based on their solutionID (which is an integer).
The code looks like this:
#Repository
public interface ProductSolutionRepository extends CrudRepository<ProductSolution, String> {
public List<ProductSolution> findBySolutionId(#Param("solutionId") int solutionId);
}
When sending a request with a string instead of an integer (localhost:8080/api/v1/productSolutions/search/findBySolutionId?solutionId=dangerousscript)the API returns an error with the following message:
Failed to convert from type [java.lang.String] to type
[#org.springframework.data.repository.query.Param int] for value
'dangerousscript'; nested exception is
java.lang.NumberFormatException: For input string:
\"dangerousscript\""`
Although it appears that Chrome and Firefox are neatly escaping the input (and don't execute any scripts) people are still afraid our API might be used for cross site scripting attacks.
A simple way to fix this would be removing the input the user gave when we throw an exception, or make our own error page. In what way can I configure spring boot to throw a custom exception?
You can define a method with #ExceptionHandler in class annotated with #ControllerAdvice.
You can either annotate this method with #ResponseBody (if you're not using #RestController) and directly return desired response or rethrow exception with correct message.
#ControllerAdvice
public class RestExceptionControllerAdvice {
#ExceptionHandler(NumberFormatException.class)
public ErrorResponse handleSearchParseException(NumberFormatException exception) {
// do whathever you want
}
}

How to sign JSON message used by Spring Rest controller?

I have a Spring REST application that accepts JSON messages, written like
#RequestMapping(value = "/myhook", method = RequestMethod.POST,
produces = JSON, consumes = JSON)
public #ResponseBody MyResponse doIt
(#Valid #RequestBody(required = true) MyContractRequest request) {
MyResponse response;
...
return response;
}
This works really well with almost no code to support, but now I have a requirement to sign both response and request.
I started from simply computing the shared signature of all message fields at Java level and assigning it to the dedicated signature field. However this requires to have and maintain code for computing the signatures:
public void update(java.security.Signature sign) throws Exception {
sign.update(name);
sign.update(value);
sign.update(etc);
}
Some people around me expressed opinion that the need to write and maintain this signing code may not be the best design, and it may be better to sign the whole message as a single JSON string. I could fetch the request as a string manually, and then process JSON manually, but I really would like to preserve the Spring controller concepts.
Also, I cannot longer have the signature field in the message itself because the value of this field obviously also changes the signature of the JSON string.
Is there any way to compute the signature of the whole JSON message body on the message departure and arrival, and where to place the signature so it could be passed together with the message? One of the idea is to use the custom HTTP header for the signature. Anyway, how to compute it first?
You can use a servlet filter with Spring MVC and modified your content whatever you want in request and response as well
Example :
http://www.mkyong.com/spring-mvc/how-to-register-a-servlet-filter-in-spring-mvc/
or you can use Spring 3 MVC Interceptor
http://viralpatel.net/blogs/spring-mvc-interceptor-example/

Categories