So far I've got an endpoint which goes as follows:
#PostMapping(path = "/my-endpoint")
public ResponseEntity<Void> method(#PathVariable("id") String id,
#RequestBody #Valid MyClass<MyType> body) {
// custom logic in here
return ResponseEntity.ok().build();
}
When performing the POST request to that endpoint, the validation when the object is wrong is performed properly and 400: Bad Request is shown.
However, now due to some code circumstances I want to trigger that method from outside the RestController and perform the same validations via a Consumer.
The new code goes as follows:
#Bean
public Consumer<Message<String>> consumer(MyController myController) {
message -> myController.method("sampleId", message); // message here is parsed to the class, so the proper type is sent to the controller method.
}
And whenever I check for the myController.method call, the code is always 200: OK, no matter what input is sent.
Is there a way to trigger validations not sent through the REST API?
I suggest to move custom logic from controller to a #Service annotated class first.
Then inject validator #Autowired private Validator validator; and trigger validation.
public void myServiceMethod(MyMessage message) {
Set<ConstraintViolation<MyMessage>> violations = validator.validate(message);
if (!violations.isEmpty()) {
// ...
}
}
https://www.baeldung.com/spring-service-layer-validation
Related
I have the following code:
class OrderController {
#AllowedScopes({ORDER_CREATE})
#PostMapping("/create")
public CreateOrderResponse createOrder(#Valid #RequestBody OrderRequest request){
}
}
#Aspect
#Component
public class AllowedScopeAspect {
#Pointcut("#annotation(allowedScopes)")
private void callAtAllowedScopes(AllowedScopes allowedScopes) {
// just a pointcut signature
}
#Before(value = "callAtAllowedScopes(allowedScopes)", argNames = "jp,allowedScopes")
public void validateScope(JoinPoint jp, AllowedScopes allowedScopes) {
...
}
}
Aspect code validates if user have required scope.
The problem is Aspect code is executed after request body validation. If validation is not OKAY, it is returning validation error. if passes, returning 403 error.
How can I execute aspect code before data binding and validation or control handler stage?
You seem to misunderstand how the JVM works. Method parameters always need to be evaluated before calling the method, otherwise the JVM cannot put the parameters on the stack for the method to get access to them. Therefore, also validation takes place before calling the method.
Spring AOP can only intercept method execution, i.e. an aspect is necessarily triggered after validation. The whole point of parameter validation is to not execute the corresponding method, if any parameter is invalid. But if the method is not executed in the first place, there is nothing to intercept for the aspect. 😉
In the Spring Framework validating the request respond with error 400 (Bad Request), specially when validating the request body and the request fields decorated with the
javax.validation.constraints.* annotations (which specified in JSR 303).
For make it more clear lets go through the example:
I have decorated the fields of the class ContactDetails with #Email and #NotEmpty constraints
class ContactDetails {
#Email
String email;
#NotEmpty
String message;
}
In the controller I used #Valid annotation to make Spring Validator validate the http request body.
#RestController
class NotificationController {
#PostMapping("/sendNotification")
public String sendNotification(#Valid #RequestBody ContactDetails contactDetails) {
...
}
}
If the validation fails, it will trigger a MethodArgumentNotValidException. By default, Spring will translate this exception to a HTTP status 400 (Bad Request).
But for validating the request params or path variables based on Spring documentations I will decorate the controller class with #Validated and just using javax.validation.constraints.* annotations on the parameters and I expect the same result same as validating the request body.
#Validated
#RestController
class NotificationController {
#GetMapping("/getContactDetailsByEmail/{email}")
public ContactDetails findContactDetails(#Email String email) {
...
}
}
In contrast to request body validation a failed validation will trigger a ConstraintViolationException instead of a MethodArgumentNotValidException. Spring does not register a default exception handler for this exception, so it will by default cause a response with HTTP status 500 (Internal Server Error).
I expected to get error 400 for this scenario and I do not know if I missed any thing in my code?
That would be great if any body can help me with this scenario why Spring has different approaches for validating the parameters.
You can create the answer you want by using the fields in the ConstraintViolationException with the following method;
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handlePathVariableError(final ConstraintViolationException exception) {
log.error(exception.getMessage(), exception);
final List<SisSubError> subErrors = new ArrayList<>();
exception.getConstraintViolations().forEach(constraintViolation -> subErrors.add(generateSubError(constraintViolation)));
final SisError error = generateErrorWithSubErrors(VALIDATION_ERROR, HttpStatus.BAD_REQUEST, subErrors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
In the first case - with argument annotated with #RequestBody - Spring uses RequestResponseBodyMethodProcessor to validate it and throws MethodArgumentNotValidException if validation fails, which is handled later by ResponseEntityExceptionHandler or DefaultHandlerExceptionResolver by translating it into 400 BAD REQUEST http response code.
In the second case Spring is using AOP for method validation with MethodValidationInterceptor class, which throws ConstraintViolationException if validation fails.
But, unlike the first case, Spring doesn't provide a default exception handler for ConstraintViolationException, so it's translated into 500 http response code.
That's why developers should consider creating their own exception handlers for this kind of method-level validation.
Is there a way to add validation to feign clients on the request parameters.
For example:
#FeignClient
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") String zipCode);
}
It would be nice to verify that zipcode is not empty and is of certain length etc, before sending the HTTP call to the server.
If your validations are simple, apply to only headers and query string parameters, you can use a RequestInterceptor for this, as it provides you the opportunity to review the RequestTemplate before it is sent to the Client.
public class ValidatingRequestInterceptor implements RequestInterceptor {
public void apply(RequestTemplate requestTemplate) {
// use the methods on the request template to check the query and values.
// throw an exception if the request is not valid.
}
}
If you need to validate the request body, you can use a custom Encoder
public class ValidatingEncoder implements Encoder {
public void encode(Object object, Type type, RequestTemplate template) {
// validate the object
// throw an exception if the request is not valid.
}
}
Lastly, if you want to validate individual parameters, you can provide a custom Expander for the parameter and validate it there. You can look at this answer for a complete explanation on how to create a custom expander that can work with Spring Cloud.
How to custom #FeignClient Expander to convert param?
For completeness, I've included an example for how to do this with vanilla Feign.
public class ZipCodeExpander implements Expander {
public String expand(Object value) {
// validate the object
// throw an exception if the request is not valid.
}
}
public interface ZipCodeClient {
#RequestLine("GET /zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#Param(expander = ZipCodeExpander.class) ("zipCode") String zipCode);
}
As pointed out in this comment, a solution using the Bean Validation API would be nice. And indeed, I found in a Spring Boot project that merely placing #org.springframework.validation.annotation.Validated on the interface is sufficient for enabling Bean Validation.
So for example:
#FeignClient
#Validated
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") #NotEmpty String zipCode);
}
triggering a ConstraintViolationException in the case of violations.
Any standard Bean Validation feature should work here.
UDPATE Note that there seems to be a potential issue with this solution that might require setting a Hibernate Validator configuration property like this: hibernate.validator.allow_parallel_method_parameter_constraint=true
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.
I was not experiencing this problem early in development but just noticed that this was happening when debugging another problem. This happens on all REST endpoints, but below is an example:
#RestController
#RequestMapping("/editlisting")
public class EditParkingSpaceListingController {
#Autowired
ParkingSpaceRepository parkingSpaceRepository;
#Autowired
ParkingSpaceListingRepository parkingSpaceListingRepository;
#RequestMapping(method = RequestMethod.PUT)
public ResponseEntity<String> editParking(#RequestBody ParkingSpaceListingClient pslc, BindingResult result) {
if (result.hasErrors()) {
return new ResponseEntity<String>("", HttpStatus.BAD_REQUEST);
}
// Code to save pslc data to database.
Now, if I send an HTTP request with the body as
{ }
I get a 200 response and when I check MongoDB, there is a new empty document in the collection. If I send an empty body with no brackets, as expected it will return 400. If I send a body with random garbage data that does not exist in the POJO, BindingResult does not seem to pick up the error and a new blank document is still created.
You need to follow the below steps for the input document validations:
(1) Add the javax.validation package constraints (like #NotNull, #Size, etc..) to your ParkingSpaceListingClient bean class.
(2) Add #Validated annotation to your controller method, to capture the validation errors into BindingResult object.
You can look here for more details on Input Validations.