I'm trying to build a small POC to check if we can use Spring Validation on our projects (REST Endpoints). The goal is to use the #Valid annotation on some Component's methods, annotate arguments with JSR-303 annotations and build some Validator instances for custom validation logic.
Consider the following scenario:
Account (Getters and Setters ommited)
public class Account {
private int id;
#Min(0) private double amount;
#NonNull private String cardholder;
}
AccountController
#RestController
#RequestMapping("/account")
public class AccountController {
#Autowired private AccountService service;
#RequestMapping(method= RequestMethod.POST)
public void post(#RequestBody Account account) {
service.save(account);
}
}
AccountService
#Component
public class AccountService {
public void save(**#Valid** Account account) {
// Logic ommited
log.info("Account saved!");
}
}
AccountSaveValidator
public class AccountSaveValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) { return Account.class.isAssignableFrom(clazz); }
#Override
public void validate(Object target, Errors errors) {
Account account = (Account) target;
if (**if account does not exist**)
errors.rejectValue("id", "account.not-exists");
}
}
Whenever I POST to /account, the mentioned validations do not run, and the Account saved! message is displayed regardless. However, if I put the #Valid annotation on the AccountController's POST handler instead, the validations are executed.
I was only able to execute only the custom validation (AccountSaveValidator) manually by calling it on the save() method like this:
ValidationUtils.invokeValidator(new AccountSaveValidator(), account, errors);
if (errors.hasErrors()) {
throw new ValidationException(errors);
}
What am I missing here? I've read that these validation components are normally used along with Spring-MVC, but that it could be used without it.
The gradle dependencies I have are the following:
compile "org.springframework:spring-core"
compile "org.springframework:spring-context"
compile "org.springframework:spring-web"
compile "org.springframework.boot:spring-boot"
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-autoconfigure"
compile "javax.validation:validation-api:1.1.0.Final"
compile "org.hibernate:hibernate-validator:5.2.4.Final"
A couple things, I believe that your title on the question here is a bit misrepresentitive of what you are actually asking here. You are not trying to validate a spring Component. You wish to do method parameter validation within a Spring Component, which is different. I believe that your question here is a duplicate of this question: JSR 303. Validate method parameter and throw exception. There are examples there of how to do what you want to do using proxies and a MethodValidationInterceptor.
I will add some additional information here to try to clarify the differences in where JSR-303 validation works and why.
Spring MVC Parameters: Parameters passed into Spring MVC Components are resolved using a combination of user defined and default HandlerParameterResolvers. Part of Spring's default MVC configuration includes hooks for automatic mappings via #RequestParam and #PathVariable to raw types and #RequestBody to Objects all via the afore mentioned HandlerParameterResolver. In the same way that these HandlerParameterResolvers are automatically configured in Spring (mostly by default) there are also Validators that are registered to the DataBinders, which map the data from the request to the params above, JSR-303 validation is automatically configured to tie into these hooks. This of course is a simplified summary of what is going on behind the scenes.
Spring Components/Beans: Spring Beans and Components are validated by a Spring Bean Validator. You can find details on this here: http://docs.spring.io/autorepo/docs/spring/3.2.x/spring-framework-reference/html/validation.html as described in the section called "7.8.2 Configuring a Bean Validation Implementation"
Related
We are in the process of migrating a legacy application to Spring Boot. In order to continue with testing until we have assigned roles to users, I would like to override the following:
class: SecurityContextHolderAwareRequestWrapper
method: public boolean isUserInRole(String role)
I have created a new class which extends SecurityContextHolderAwareRequestWrapper and overrides isUserInRole(), as follows:
#Component
public class MySecurityContextHolderAwareRequestWrapper extends org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper {
public MySecurityContextHolderAwareRequestWrapper(HttpServletRequest request,
AuthenticationTrustResolver trustResolver, String rolePrefix) {
super(request, trustResolver, rolePrefix);
}
#Override
public boolean isUserInRole(String role) {
return true;
}
When the application is run, the new bean does not take the place of the existing SecurityContextHolderAwareRequestWrapper class. This is clear because when the new class is instantiated, the constructor is not injected with the beans being injected into SecurityContextHolderAwareRequestWrapper. The application fails to start because parameters of type AuthenticationTrustResolver and String to the new class MySecurityContextHolderAwareRequestWrappercould could not be found
What is the correct way to override SecurityContextHolderAwareRequestWrapper, or for that matter any class in the Spring Boot framework?
Thanks
The SecurityContextHolderAwareRequestWrapper class is ultimately used by the SecurityContextHolderAwareRequestFilter configured with http.servletApi(). Some information about this feature is available in the Spring Security reference docs.
This feature shields you from direct dependence on Spring Security and provides very high level integration with Spring Security through the Servlet API. You cannot directly influence the class used to wrap the request.
However, if you wish to temporarily modify the result of role checks, you can influence what roles are available in the Authentication object during authentication itself. See info in the docs on GrantedAuthority, and note that you will want to customize roles during authentication by providing a custom UserDetailsService.
I have several controller functions separated by role, and instead of doing role validation in each controller method, I found that it seems to be able to get done by using Aspect, however something isn't right in my implementation as the code in Aspect never runs
Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ForMerchantOnly {}
Aspect:
#Aspect
#Configuration
public class ForMerchantOnlyAspect {
private static final Logger logger = LogManager.getLogger(ForMerchantOnlyAspect.class);
#Before("#annotation(com.example.api.annotation.ForMerchantOnly) && args(request)")
public void before(HttpServletRequest request) throws ServiceException {
if (!(request instanceof HttpServletRequest)) {
throw new RuntimeException("request should be HttpServletRequesttype");
}
String domain = request.getServerName();
System.out.println("Aspect showing domain " + domain);
// -- other code
}
}
Controller
#ForMerchantOnly
#GetMapping("/list")
public ResponseEntity<ApiResp> list() {
System.out.println("Show something");
return ResponseEntity.ok().body();
}
I'm assuming when i call controller /list method via chrome browser, it would hit the code in ForMerchantOnlyAspect but it just went into the controller method directly. Am I missing something?
The Aspect was not working as it could not find a matching joinpoint . There are no controller methods that has annotation #ForMerchantOnly and has an argument of type HttpServletRequest
From the documentation :
args: Limits matching to join points (the execution of methods when
using Spring AOP) where the arguments are instances of the given
types.
Following aspect may be used for the requirement . Scoping designator within will set the scope to advice.
#Before("#annotation(com.example.api.annotation.ForMerchantOnly) && within(com.example.api..*)")
public void before() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
System.out.println("Aspect showing domain " + request.getServerName());
}
Also note that an Aspect is better annotated with #Component and #Configuration be used for configurations.
You may also have a look at Method Security of Spring security framework , which lets to secure a method with annotations.
From the documentation
From version 2.0 onwards Spring Security has improved support
substantially for adding security to your service layer methods. It
provides support for JSR-250 annotation security as well as the
frameworkâs original #Secured annotation. From 3.0 you can also make
use of new expression-based annotations. You can apply security to a
single bean, using the intercept-methods element to decorate the bean
declaration, or you can secure multiple beans across the entire
service layer using the AspectJ style pointcuts.
In Springboot project, when I try to add #Validated on controller method, it worked.But now I want to add it on a common method, then failed.
Try to add #Validated on controller method, it worked
public class TaskValidator {
private static final Logger logger = LogManager.getLogger("TaskValidatorLogger");
public void validateTest(#Validated Test test) {
logger.info("Validate: {}", test.getName());
}
public static void main(String[] args) {
new TaskValidator().validateTest(new Test());
}
}
#Data
public class Test {
#NotNull(message = "name can not be null")
private String name;
}
It should throw an MethodArgumentNotValidException but not.
Spring MVC has the ability to automatically validate #Controller
inputs. In previous versions it was up to the developer to manually
invoke validation logic.
In the controller methods, springboot automatically binds any validators to the model and invoke it when the data is bound to the object.
But in your case , you are trying to validate an object in which case , springboot might not be automatically binding your validator to your model and call the validator.So, in that case, you will need to manually bind the object to the validator.
or you can manually invoke the validator on a bean like :
#AutoWired
Validator validator;
...
validator.validate(book);
I want to create an custom method argument Resolver using Spring WebFlux. I am following link but its seem to be not working.
I am able to create the custom argument resolver using WebMvc.
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
public class MyContextArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyCustomeObject.class.isAssignableFrom(parameter.getParameterType())
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
.....
return Mono.just(new MyCustomeObject())
}
Please note that i am using HandlerMethodArgumentResolver from .web.reactive. package.
My AutoConfiguration file look like
#Configuration
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the class-path
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)//checks that the app is a reactive web-app
public class RandomWebFluxConfig implements WebFluxConfigurer {
#Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
MyContextArgumentResolver[] myContextArgumentResolverArray = {contextArgumentResolver()};
configurer.addCustomResolver(myContextArgumentResolverArray );
}
#Bean
public MyContextArgumentResolver contextArgumentResolver() {
return new MyContextArgumentResolver ();
}
My spring.factories looks like
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.XXXX.XXX.XXX.RandomWebFluxConfig
Please note that above configuration is part of the jar which is added in Spring WebFlux Boot project enabled using #EnableWebFlux .
It seems you're conflating two different problems here.
First, you should make sure that your method argument resolver works in a regular project.
For that, you need a #Configuration class that implements the relevant method in WebFluxConfigurer. Your code snippet is doing that but with two flaws:
Your configuration is using #EnableWebFlux, which is disabling the WebFlux auto-configuration in Spring Boot. You should remove that
it seems you're trying to cast a list of MethodArgumentResolver into a single instance and that's probably why things aren't working here. I believe your code snippet could be just:
configurer.addCustomResolver(contextArgumentResolver());
Now the second part of this question is about setting this up as a Spring Boot auto-configuration. I guess that you'd like WebFlux applications to automatically get that custom argument resolvers if they depend on your library.
If you want to achieve that, you should first make sure to read up a bit about auto-configurations in the reference documentation. After that, you'll realize that your configuration class is not really an auto-configuration since it will be applied in all cases.
You should probably add a few conditions on that configuration like:
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the classpath
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) // checks that the app is a reactive web app
I am studying a spring exmaple. And I found the following code. I can't understand the construct function. A validator interface is passed to this function. How is this validator generated? Thanks in advance.
#Controller
#RequestMapping(value="/account")
public class AccountController {
private Map<Long, Account> accounts = new ConcurrentHashMap<Long, Account>();
private Validator validator;
#Autowired
public AccountController(Validator validator) {
this.validator = validator;
}
#RequestMapping(method=RequestMethod.POST)
public #ResponseBody Map<String, ? extends Object> create(#RequestBody Account account, HttpServletResponse response) {
Set<ConstraintViolation<Account>> failures = validator.validate(account);
if (!failures.isEmpty()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return validationMessages(failures);
} else {
accounts.put(account.assignId(), account);
return Collections.singletonMap("id", account.getId());
}
}
The validator can be any class that implements Springs Validator interface and is available in the current application context. By default Spring autowires by type. This means that every bean which implements the Validator interface can satisfy the constructor of AccountController. I don't know the exact example but there might be something like an AccountValidator bean available int the application context.
Be aware that this approach could cause problem if multiple validator beans are available in the application context.
See the Validation section in the spring documentation for details on the Validator interface.
When You use Autowired on constructor Spring looks for Validator implementation/
In this example Spring injects its default Validator implementation:
org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
Here's more:
section: 5.7.2.1 Injecting a Validator
http://docs.spring.io/spring/docs/3.0.0.RC3/reference/html/ch05s07.html
There must be some concrete implementation of Validator.Without that it is not possible.
Further to support my answer see that it is being #Autowired.
Please post the link from which you are studying or check the source code for the presence of validaor class.
Also,check this link There is no concrete implementation by default.