#InitBinder in spring boot not working with #RequestBody - spring

If I use #InitBinder without limiting it,it is working fine with #RequestBody to validate my objects.
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
#RequestMapping(method=RequestMethod.POST)
public CustomerQuickRegisterEntity saveNewCustomer(#Valid #RequestBody CustomerQuickRegisterEntity customerEntity,BindingResult result)
{
if(result.hasErrors())
{
return new CustomerQuickRegisterEntity();
}
return customerQuickRegisterRepository.save(customerEntity);
}
But problem is that when i limit it to just one object by doing it as #InitBinder("customerEntity") it is not validating the object. So I have searched through stackoverflow and found that #InitBinding only works with objects annotated with #ModelAttribute. Then my question is that it is working fine with #RequestBody when I use it as #InitBinder but does not work well when I use it as #InitBinder("customerEntity") ...why is it so?
Is there any other way to validate Objects(Not properties of object Individually) associated with #RequestBody

This is an old question, but I've managed to get the #InitBinder annotation to bind my custom Validator to a #Valid #RequestBody parameter like this:
#InitBinder
private void bindMyCustomValidator(WebDataBinder binder) {
if ("entityList".equals(binder.getObjectName())) {
binder.addValidators(new MyCustomValidator());
}
}
If you try to filter the bound argument by setting the value of the annotation, then it won't work for a #RequestBody argument. So here I check the object name instead. My method parameter is actually called entities, but Spring had decided to call it entityList. I had to debug it to discover this.

From the docs,
Default is to apply to all command/form attributes and all request
parameters processed by the annotated handler class. Specifying model
attribute names or request parameter names here restricts the
init-binder method to those specific attributes/parameters, with
different init-binder methods typically applying to different groups
of attributes or parameters.
Please have a look here

You can try my solution:
#InitBinder
private void initBinder(WebDataBinder binder) {
if (CustomerQuickRegisterEntity.class.equals(binder.getTarget().getClass())) {
binder.addValidators(new YourValidator());
}
}

Related

How to validate request parameters on feign client

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

Spring Boot Groovy Templates Not Adding _csrf to model

I have a very simple Spring Boot Application which consists of a main Application.java (with the default main method), a MainController (which has one requestMapping to /login), and a SecurityConfig (with mainly default values).
My problem is with the _csrf support in Groovy Templates. Everything works fine with FreeMarker, but when I switch to GroovyTemplates the _csrf param does not get put into the model.
Is there a bug in the Groovy Templates, something manually I have to do to grab the token, or is there some configuration step I'm missing (although I don't know why it would work for FreeMarker) ?
UPDATE:
I printed this.properties (HashMap) on the login.tpl (Groovy Template) :
{class=class login, out=java.io.BufferedWriter#5e2aead3, model={error=Optional.empty, org.springframework.validation.BindingResult.error=org.springframework.validation.BeanPropertyBindingResult: 0 errors, spring=org.springframework.web.servlet.support.RequestContext#1d99fb33, springMacroRequestContext=org.springframework.web.servlet.support.RequestContext#7fcc5c78}}
The model key in the properties map includes the parameters
I added error in the Controller action using the:
#RequestMapping(value="/login", method = RequestMethod.GET)
public ModelAndView login(#RequestParam Optional<String> error) {
return new ModelAndView("views/login", "error", error);
}
When using the GroovyMarkupView and the GroovyMarkupViewResolver the properties of the view only contain properties available in the model (and some added for Groovy).
To include the request attributes set the exposeRequestAttributes property of the GroovyMarkupViewResolver to true. Ideally this is done by setting the following property in the application.properties.
spring.groovy.template.exposeRequestAttributes=true
However due to this issue that currently isn't possible.
To work around it create a BeanPostProcessor which check if the incoming bean is a GroovyMarkupViewResolver (or AbstractTemplateViewResolver if you want a more general approach). If so set the exposeRequestAttributes to true.
public class TemplateViewResolverPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instance GroovyMarkupViewResolver) {
((GroovyMarkupViewResolver) bean).setExposeRequestAttributes(true);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
After doing that the CsfrToken is available with the key _csfr, be aware that this is the actual CsfrToken.
Another solution is to create a HandlerInterceptor implement the postHandle method and add the _csfr property to the model. That way you can simply add the value of the token instead of the actual token itself. This will work with any view technology used.
public class CsrfAddingInterceptor extends HandlerInterceptorAdapter {
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView mav) throws Exception {
CsrfToken token = (CsrfToken) req.getAttribute(CsrfToken.class.getName())
if (token != null) {
mav.addAttribute(token.getParameterName(), token.getToken());
}
}
}
Then add it as an interceptor and you will have the value available.
So the hacked up way to handle this is to manually add the _csrf attribute to your model in your Controller. For example:
model.addAttribute( "_csrf", request.getAttribute("_csrf") );
I wouldn't recommend that if you have lots of views on your server. I'd suggest you follow one of the options from #M-Deinnum. But, for quick testing this works.

Spring validation keeps validating the wrong argument

I have a controller with a web method that looks like this:
public Response registerDevice(
#Valid final Device device,
#RequestBody final Tokens tokens
) {...}
And a validator that looks like this:
public class DeviceValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Device.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
// Do magic
}
}
}
I'm trying to get Spring to validate the Device argument which is being generated by an interceptor. But every time I try, it validates the tokens argument instead.
I've tried using #InitBinder to specify the validator, #Validated instead of #Validand registering MethodValidationPostProcessor classes. So far with no luck.
Either the validator is not called at all, or tokens argument is validated when I was the Device argument validated.
I'm using Spring 4.1.6 and Hibernate validator 5.1.3.
Can anyone offer any clues as to what I'm doing wrong? I've searched the web all afternoon trying to sort this out. Can't believe that the validation area of spring is still as messed up as it was 5 years ago :-(
Ok. Have now solved it after two days of messing about with all sorts of variations. If there is one thing Spring's validation lets you do - it's come up with an incredible array of things that don't work! But back to my solution.
Basically what I needed was a way to manually create request mapping arguments, validate them and then ensure that no matter whether it was a success or failure, that the caller always received a custom JSON response. Doing this proved a lot harder than I thought because despite the number of blog posts and stackoverflow answers, I never found a complete solution. So I've endeavoured to outline each piece of the puzzle needed to achieve what I wanted.
Note: in the following code samples, I've generalised the names of things to help clarify whats custom and whats not.
Configuration
Although several blog posts I read talked about various classes such as the MethodValidationPostProcessor, in the end I found I didn't need anything setup beyond the #EnableWebMvc annotation. The default resolvers etc proved to be what I needed.
Request Mapping
My final request mapping signatures looked like this:
#RequestMapping(...)
public MyMsgObject handleRequest (
#Valid final MyHeaderObj myHeaderObj,
#RequestBody final MyRequestPayload myRequestPayload
) {...}
You will note here that unlike just about every blog post and sample I found, I have two objects being passed to the method. The first is an object that I want to dynamically generate from the headers. The second is a deserialised object from the JSON payload. Other objects could just as easily be included such as path arguments etc. Try something like this without the code below and you will get a wide variety of weird and wonderful errors.
The tricky part that caused me all the pain was that I wanted to validate the myHeaderObj instance, and NOT validate the myRequestPayload instance. This caused quite a headache to resolve.
Also note the MyMsgObject result object. Here I want to return an object that will be serialised out to JSON. Including when exceptions occur as this class contains error fields that need to be populated in addition to the HttpStatus code.
Controller Advice
Next I created an ControllerAdvice class which contained the binding for validation and a general error trap.
#ControllerAdvice
public class MyControllerAdvice {
#Autowired
private MyCustomValidator customValidator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
if (binder.getTarget() == null) {
// Plain arguments have a null target.
return;
}
if (MyHeaderObj.class.isAssignableFrom(binder.getTarget().getClass())) {
binder.addValidators(this.customValidator);
}
}
#ExceptionHandler(Exception.class)
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public MyMsgObject handleException(Exception e) {
MyMsgObject myMsgObject = new MyMsgObject();
myMsgObject.setStatus(MyStatus.Failure);
myMsgObject.setMessage(e.getMessage());
return myMsgObject;
}
}
Two things going on here. The first is registering the validator. Note that we have to check the type of the argument. This is because #InitBinder is called for each argument to the #RequestMapping and we only want the validator on the MyHeaderObj argument. If we don't do this, exceptions will be thrown when Spring attempts to apply the validator to arguments it's not valid for.
The second thing is the exception handler. We have to use #ResponseBody to ensure that Spring treats the returned object as something to be serialised out. Otherwise we will just get the standard HTML exception report.
Validator
Here we use a pretty standard validator implementation.
#Component
public class MyCustomValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyHeaderObj.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
...
errors.rejectValue("fieldName", "ErrorCode", "Invalid ...");
}
}
One thing that I still don't really get with this is the supports(Class<?> clazz) method. I would have thought that Spring uses this method to test arguments to decide if this validator should apply. But it doesn't. Hence all the code in the #InitBinder to decide when to apply this validator.
The Argument Handler
This is the biggest piece of code. Here we need to generate the MyHeaderObj object to be passed to the #RequestMapping. Spring will auto detect this class.
public class MyHeaderObjArgumentHandler implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyHeaderObj.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
// Code to generate the instance of MyHeaderObj!
MyHeaderObj myHeaderObj = ...;
// Call validators if the argument has validation annotations.
WebDataBinder binder = binderFactory.createBinder(webRequest, myHeaderObj, parameter.getParameterName());
this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
throw new MyCustomException(myHeaderObj);
}
return myHeaderObj;
}
protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
Annotation[] annotations = methodParam.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] { hints });
binder.validate(validationHints);
break;
}
}
}
}
The main job of this class is to use whatever means it requires to build the argument (myHeaderObj). Once built it then proceeds to call the Spring validators to check this instance. If there is a problem (as detected by checking the returned errors), it then throws an exception that the #ExceptionHandler's can detect and process.
Note the validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) method. This is code I found in a number of Spring's classes. It's job is to detect if any argument has a #Validated or #Valid annotation and if so, call the associated validators. By default, Spring does not do this for custom argument handlers like this one, so it's up to us to add this functionality. Seriously Spring ???? No AbstractSomething ????
The last piece, explicit Exception catches
Lastly I also needed to catch more explicit exceptions. For example the MyCustomException thrown above. So here I created a second #ControllerAdvise.
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE) // Make sure we get the highest priority.
public class MyCustomExceptionHandler {
#ExceptionHandler
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public Response handleException(MyCustomException e) {
MyMsgObject myMsgObject = new MyMsgObject();
myMsgObject.setStatus(MyStatus.Failure);
myMsgObject.setMessage(e.getMessage());
return myMsgObject;
}
}
Although superficially the similar to the general exception handler. There is one different. We need to specify the #Order(Ordered.HIGHEST_PRECEDENCE) annotation. Without this, Spring will just execute the first exception handler that matches the thrown exception. Regardless of whether there is a better matching handler or not. So we use this annotation to ensure that this exception handler is given precedence over the general one.
Summary
This solution works well for me. I'm not sure that I've got the best solution and there may be Spring classes which I've not found which can help. I hope this helps anyone with the same or similar problems.

Adding multiple validators using initBinder

I'm adding a user validator using the initBinder method:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
Here is the UserValidator
public class UserValidator implements Validator {
public boolean supports(Class clazz) {
return User.class.equals(clazz);
}
public void validate(Object target, Errors errors) {
User u = (User) target;
// more code here
}
}
The validate method is getting properly called during the controller method call.
#RequestMapping(value = "/makePayment", method = RequestMethod.POST)
public String saveUserInformation(#Valid User user, BindingResult result, Model model){
// saving User here
// Preparing CustomerPayment object for the payment page.
CustomerPayment customerPayment = new CustomerPayment();
customerPayment.setPackageTb(packageTb);
model.addAttribute(customerPayment);
logger.debug("Redirecting to Payment page.");
return "registration/payment";
}
But while returning to the payment screen I'm getting this error:
java.lang.IllegalStateException: Invalid target for Validator [com.validator.UserValidator#710db357]: com.domain.CustomerPayment[ customerPaymentId=null ]
org.springframework.validation.DataBinder.setValidator(DataBinder.java:476)
com.web.UserRegistrationController.initBinder(UserRegistrationController.java:43)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
java.lang.reflect.Method.invoke(Method.java:597)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.initBinder(HandlerMethodInvoker.java:393)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.updateModelAttributes(HandlerMethodInvoker.java:222)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:429)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:414)
This might be because I'm returning a CustomerPayment and there is not validator defined for that.
I'm also not able to add multiple validators in initBinder method.
How can I fix this?
You need to set the value of the #InitBinder annotation to the name of the command you want it to validate. This tells Spring what to apply the binder to; without it, Spring will try to apply it to everything. This is why you're seeing that exception: Spring is trying to apply the binder - with your UserValidator - to a parameter of type CustomerPayment.
In your specific case, it looks like you need something like:
#InitBinder("user")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
To your second question, as Rigg802 explained, Spring does not support attaching multiple validators to a single command. You can, however, define multiple #InitBinder methods for different commands. So, for example, you could put the following in a single controller and validate your user and payment parameters:
#InitBinder("user")
protected void initUserBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
#InitBinder("customerPayment")
protected void initPaymentBinder(WebDataBinder binder) {
binder.setValidator(new CustomerPaymentValidator());
}
It's a bit tricky to do, 1 controller has only 1 validator on 1 command object.
you need to create a "Composite Validator" that will get all the validators and run them seperately.
Here is a tutorial that explains how to do it: using multiple validators
You can add multiple validators by iterating over all org.springframework.validation.Validator in an ApplicationContext and set up suitable ones in #InitBinder for each request.
#InitBinder
public void setUpValidators(WebDataBinder webDataBinder) {
for (Validator validator : validators) {
if (validator.supports(webDataBinder.getTarget().getClass())
&& !validator.getClass().getName().contains("org.springframework"))
webDataBinder.addValidators(validator);
}
}
See my project for examples and simple benchmarks. https://github.com/LyashenkoGS/spring-mvc-and-jms-validation-POC/tree/benchamark
I do not see a reason why Spring does not filter out all validators which are not applicable to the current entity by default which forces to use things like CompoundValidator described by #Rigg802.
InitBinder allows you to specify name only which give you some control but not full control over how and when to apply your custom validator. Which from my perspective is not enough.
Another thing you can do is to perform check yourself and add validator to binder only if it is actually necessary, since binder itself has binding context information.
For example if you want to add a new validator which will work with your User object in addition to built-in validators you can write something like this:
#InitBinder
protected void initBinder(WebDataBinder binder) {
Optional.ofNullable(binder.getTarget())
.filter((notNullBinder) -> User.class.equals(notNullBinder.getClass()))
.ifPresent(o -> binder.addValidators(new UserValidator()));
}
There is a simple hack, always return true in supports method, and delegate the class checking to validate. Then basically you can add multiple validator in the initBinder without issue.
#Component
public class MerchantRegisterValidator implements Validator {
#Autowired
private MerchantUserService merchantUserService;
#Autowired
private MerchantCompanyService merchantCompanyService;
#Override
public boolean supports(Class<?> clazz) {
return true; // always true
}
#Override
public void validate(Object target, Errors errors) {
if (!RegisterForm.getClass().equals(target.getClass()))
return; // do checking here.
RegisterForm registerForm = (RegisterForm) target;
MerchantUser merchantUser = merchantUserService.getUserByEmail(registerForm.getUserEmail());
if (merchantUser != null) {
errors.reject("xxx");
}
MerchantCompany merchantCompany = merchantCompanyService.getByRegno(registerForm.getRegno());
if (merchantCompany != null) {
errors.reject("xxx");
}
}
}
Multiple validator on one command is supported with Spring MVC 4.x now. You could use this snippet code:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new UserValidator(), new CustomerPaymentValidator());
}
The safest way is to add a generic validator handling that Controller:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(new GenericControllerOneValidator());
}
Then, in the generic validator you can support multiple request body models and based of the instance of the object, you can invoke the appropriate validator:
public class GenericValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return ModelRequestOne.class.equals(aClass)
|| ModelRequestTwo.class.equals(aClass);
}
#Override
public void validate(Object body, Errors errors) {
if (body instanceof ModelRequestOne) {
ValidationUtils.invokeValidator(new ModelRequestOneValidator(), body, errors);
}
if (body instanceof ModelRequestTwo) {
ValidationUtils.invokeValidator(new ModelRequestTwoValidator(), body, errors);
}
}
}
Then you add your custom validations inside for each model validator implementatios. ModeRequestOneValidator and ModeRequestTwoValidator still need to implement the Validator interface of org.springframework.validation
Also, do not forget to use #Valid ModeRequestOne and #Valid ModeRequestTwo inside the controllers method call.
One addition to Annabelle's answer:
If controller has this method parameter and you want to validate that one specifically
#RequestMapping(value = "/users", method = RequestMethod.POST)
public String findUsers(UserRequest request){..}
Then the binding should be lower case of the class name (but just the first letter, and not everything else)
#InitBinder("userRequest")
protected void initUserBinder(WebDataBinder binder) {
binder.setValidator(new YourValidator());
}
Declare request as
(... , Model model,HttpServletRequest request)
and change
model.addAttribute(customerPayment);
to
request.setAttribute("customerPayment",customerPayment);

Using annotation to bind data

Is it possible to do the same kind of behavior as this :
protected void onBind(HttpServletRequest request, Object command, BindException bindException) throws Exception {
Invoice invoice = (Invoice) command;
invoice.getLineItems().removeAll(Collections.singletonList(null));
}
When using annotation ?
I'm using the #controller annotation so i don't have the onBind function. I want to manipute a List of element (remove item from the list).
New way is to annotate method with #InitBinder annotation:
#InitBinder
protected void initBinder(WebDataBinder binder) {
...
}
Also you may register PropertyEditor for transforming values by calling WebDataBinder.registerCustomEditor method. Also for transforming to collections there is CustomCollectionEditor which may be convenient.

Resources