How to restrict JSON payload from containing additional fields with Spring? - spring

I have a basic User DTO class...
public class User {
#JsonProperty("firstName")
private String firstName;
#JsonProperty("lastName")
private String lastName;
}
...and a basic request handler in a #RestController class:
#RequestMapping(path = "/users", method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE })
public UserMessage createUser(#RequestBody User user){
return userService.createUser(user);
}
How can I restrict incoming JSON payloads to contain at most only the required keys?
i.e. accept this payload:
{
"firstName":"foo",
"lastName":"bar"
}
And throw a custom exception on this:
{
"firstName":"foo",
"lastName":"bar",
"type":"admin",
"asdf":"asdf"
}
I read about custom Converters, ArgumentResolvers, and I believe I could simply put an additional Map parameter in the handler and validate before service call, however I'd like to know the "best" way of handling this issue.

Regarding the User bean in your example it also already not possible, that potential other JSON fields than firstName and lastName could be mapped, simply because there are no fields in User which could hold the relevant data.
Should the User bean in your question be not complete, e.g. for simplicity reasons, and contain more fields, also then should everything be fine, as long as you did not configure your your ObjectMapper with com.fasterxml.jackson.databind.DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES => false or you use the annotation #JsonIgnoreProperties(ignoreUnknown = true) on your bean.
To sum it up: Jackson's default behavior is FAIL_ON_UNKNOWN_PROPERTIES (default: true)
For further information you can also consult the respective Deserialization docs.

Solved the issue, this thread helped
#JsonIgnoreProperties(ignoreUnknown=false) is not working in Spring 4.2.0 and upper version
mle, your answer wasn't right, since I was using the latest version of Spring Framework and the ObjectMapper's FAIL_ON_UNKNOWN_PROPERTIES is turned off by default. Additionally I was needed to set #JsonIgnoreProperties(ignoreUnknown = false) in my User DTO class (as the actual class' superclass had this set to true).
Tested it, runs like a charm, while custom errors can be handled in a #ExceptionHandler(HttpMessageNotReadableException.class) annotated handler.

Related

Spring Boot Validation - Request parameter

I'm having an issue with Spring Boot validation on a REST controller. I'm using validation 2.4.1.
I'm trying to validate elements of a list. Here's an example:
#Validated
#RestController
#RequestMapping("path")
class ControllerClass(
private val service: ServiceClass
) {
#GetMapping()
suspend fun controllerMethod(
request: ServerHttpRequest,
#RequestParam(required = false)
#NotEmpty
id: List<#Positive Int>? // Here's the issue.
): ResponseEntity<Map<String, Any>> {
// Content...
}
}
#NotEmpty works properly on the list object, so when the parameter is empty (example.com/path?id=), the client receives a validation error message. However, #Positive doesn't work on the Int objects inside the list. I did some research and some people say it works. I also check the #Positive interface and it supposed to work like that.
I also tried using #Valid everywhere, with the type use, parameter and method, but it doesn't work either.
Is there anything else I have to do? For example, I know that, to use validation on data classes in Kotlin, you have to use #get: (#get:Positive, for example) for validation to work.
Thank you!

Providing default values for validation in SpringBoot

I want SpringBoot to be able to provide default values for fields that the user must enter. For example, I have something like this:
*Controller class*
#PostMapping("/test")
public ResponseEntity<> myMethod(#RequestBody #Valid MyContract contract) {}
*MyContract class*
#Valid
DataObject dataObject;
*DataObject class*
#Component
public class DataObject {
private #Value("${field1.default}") String field1Default;
private String field1
public String getField1() {
return (field1 == null ? field1Default : field1);
}
}
The DataObject class needs to be created on a per request basis. There are also other places in the code where it needs to be created on demand. So I imagine it needs to be a Prototype object. But I can't figure out how to get Spring to created it properly when it creates it for the request.
Update
I have read more about #RequstBody, e.g., https://www.javadevjournal.com/spring/spring-request-response-body/ and Should spring #RequestBody class be singleton or prototype?, which explains that the object is not a Component, but a simple POJO that gets the values from the Json request. So it seems that there is no way to inject #Values from the Spring application.properties file. Is there any other way around this? Or another suggested implementation?

Spring REST #RequestBody consume (XML or JSON) to POJO without annotations

I am writing a Springboot REST endpoint and want to consume XML or JSON requests for a simple service. In either case I want Spring to construct an #RequestBody pojo WITHOUT annotating any of the POJO. Is this OK? Safe? Performant?
I was reading this which told me about configuration by exception. To me this means if I structure my request to contain the exact name and case as the POJO member variables I want to populate the #RequestBody will be able to create my class SomeRequest.
If this is my REST endpoint:
#RequestMapping(value = GET_FOR_SOMETHING, method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE},,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
StatusResponse<Boolean> getMpdForReqest(#RequestBody SomeRequest request)
And this is my POJO:
public class SomeRequest {
String one;
String two;
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
public String getTwo() {
return two;
}
public void setTwo(String two) {
this.two = two;
}
}
My JSON request:
{
"one": "str",
"two": "str"
}
My XML request:
<SomeRequest>
<one>str</one>
<two>str</two>
</SomeRequest>
My question is: why should I not do this or is it perfectly fine?
Thank you all.
TLDR; It is perfectly fine.
Is this OK? Safe? Performant?
Yes, it is as performant as it's annotated cousin, if you take program efficiency into account.
If you take the Programmer efficiency into account, it is much more efficient as the developer doesn't have to deal with a bunch of annotations.
Speaking of Programmer efficiency, I would encourage you to use project Lombok instead of crapping your POJO with bunch of getter and setter methods, that's what cool kids do now a days.
Catch
This will work fine as long as your json fields are one word and small case.
When you have multi-word field name, Java standard is the camelCase and usually JSON standard is the snake_case. In this case, you can either have a Class level Annotation (one per class, so not much ugly). Or, since you are using spring boot, you can use an application wide property (spring.jackson.property-naming-strategy = SNAKE_CASE ).
If you have weird json field names with spaces in between, you might need to use #JsonProperty annotation. Remember, this is a perfectly valid json
{
"just a name with a space" : "123"
}
POJO as RequestBody works perfectly fine. Just note that Spring however will return 400 - Bad Request for every request that can not be mapped to the #RequestBody annoted object.

How to auto generate response fields that do not have POJO

We have a service that simply returns the json document on a GET request. Since we do not have the POJO for the response "model", it appears we won't be able to use the auto response fields generation "goodness".
One option for us is to create the Pojos (quite large, about 50 attributes) and a corresponding controller that uses the pojos. This is awkward as we now have to maintain the model and corresponding controller just so we can auto generate the model.
Any ideas on how we can still leverage some auto generation of the response fields would be greatly appreciated.
Here's the controller I'm referring to:
#RestController
#RequestMapping("/api")
public class ProductController {
#Autowired
ProductService productService;
#RequestMapping(value = { "/products/{ids}" }, method = { RequestMethod.GET },
produces = "application/json", headers={"accept=application/json"})
#Timed
#ExceptionMetered
#LogExecutionTime
public String getProductDetails(#PathVariable("id") String id) {
return productService.getProductDetails(id);
}
At the moment I see no way of leveraging the auto generation without putting additional effort into it. Spring Auto REST Docs works by inspecting POJOs with a Jackson visitor (static introspection without runtime information) and there is currently no way of deriving the JSON fields from a string (would be dynamic at runtime). Thus, I only see two options:
The approach that you already described: Creating the corresponding POJO and using it.
Using Spring REST Docs for the corresponding test and manually document each field in the test. Might be the better option here if you do not want to alter the production code.

Spring 5 Webflux functional endpoints - How to perform input validation?

According to the current doc (5.0.0.RELEASE) Spring Webflux supports validation when working with annotated controllers:
By default if Bean Validation is present on the classpath — e.g.
Hibernate Validator, the LocalValidatorFactoryBean is registered as a
global Validator for use with #Valid and Validated on #Controller
method arguments.
However nothing is said about how to automate it with functional endpoints. In fact, the only example of input processing in the documentation doesn't validate anything:
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ServerResponse.ok().build(repository.savePerson(person));
}
Are we supposed to do this manually or there is some automatic way to do it?
In Spring version 5.0, there is no automatic way to do validation in functional endpoints, and as such validation must be done manually.
Though there are currently no concrete plans to do so, we might add some sort of validation in the future. But even then it will be an explicit method call, and not an automatic mechanism. Overall, the functional endpoint model is designed to be a lot more explicit than the annotation-based model.
As arjen-poutsma said, it seems there is no way of running automated validations on Spring 5 functional endpoints.
Spring documentation is not very clear about this, and it doesn't suggest any approach.
On this Baeldung article, you'll find an idea on how you can run validations using this approach (disclaimer: I'm the writer of the article :) )
In a nutshell, you can follow these steps:
Implement Spring Validators to evaluate your resources
Create an abstract class with the basic procedure that any handler will follow when processing a request, leaving up to the children classes what to do when the data is valid
Make your request handler classes extend this abstract class, implementing this abstract method, stating the body it will be expecting, and what validator needs to be used to validate it
EDIT:
I've been following this related Spring issue, and it seems we now count with official documentation regarding this subject: https://github.com/spring-projects/spring-framework/blob/master/src/docs/asciidoc/web/webflux-functional.adoc#validation
The suggested approach is to use validators as explained in the article.
At the current version(2.0.4.RELEASE) there isn't a way to do automatic validation with handles, however you always could make a manual validation like this:
#Slf4j
#Component
#FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
#RequiredArgsConstructor
public class MyHandlerValidator implements HandlerValidator<MyResource> {
Validator validator;
#Override
public void callValidator(final MyResource fdr) {
final DataBinder binder = new DataBinder(fdr);
binder.setValidator(validator);
binder.validate();
if (binder.getBindingResult().hasErrors()) {
final String reason = binder.getBindingResult().getFieldError().toString();
log.error(reason);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, reason);
}
}
}
The thing with this, its that the you should throw a WebExchangeBindException like automatic validation does, however i could't create a MethodParameter witch is a dependency to create this exception.
UPDATE:
Spring show us a way to do it, which is similar to my solution, but, not enough in my opinion on documentation
Just to demo some working code. If you need simple validation based on the object annotations like:
#Value
#Builder
#Jacksonized
public class SigninRequest {
#NotBlank(message = "The username is mandatory")
#Email(message = "The username should be valid Email")
String username;
#NotBlank(message = "The password is mandatory")
String password;
}
At the handler you need just one simple additional operator doOnNext:
#Component
#RequiredArgsConstructor
public class AuthHandler {
private final AuthService authService;
private final ObjectValidator validator;
public Mono<ServerResponse> signin(ServerRequest request) {
return ok().body(
request.bodyToMono(SigninRequest.class)
.doOnNext(validator::validate) //<-- just one single line
.flatMap(login -> authService.authenticate(login.getUsername(), login.getPassword())),
AuthResult.class);
}
}
The ObjectValidator is doing actual validation and throws the runtime exception with the 4xx error in case of validation errors:
#Component
#RequiredArgsConstructor
public class ObjectValidator {
private final Validator validator;
public <T> T validate(T object) {
var errors = validator.validate(object);
if (errors.isEmpty()) {
return object;
} else {
String errorDetails = errors.stream().map(er -> er.getMessage()).collect(Collectors.joining(", "));
throw new ObjectValidationException(errorDetails);
}
}
}
And the exception:
#ResponseStatus(code = HttpStatus.UNPROCESSABLE_ENTITY)
public class ObjectValidationException extends RuntimeException {
public ObjectValidationException(String errorDetails) {
super("Please supply the valid data: " + errorDetails);
}
}
If you properly setup global error handling you can keep you handler code clean and reuse the object validator across all your handlers.

Resources