I have the following classes used for validating a password.
public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> {
@Override
public void initialize(ValidPassword constraintAnnotation) {
}
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
PasswordValidator validator = new PasswordValidator(Arrays.asList(
// at least 8 characters
new LengthRule(8, 30),
// at least one upper-case character
new CharacterRule(EnglishCharacterData.UpperCase, 1),
// at least one lower-case character
new CharacterRule(EnglishCharacterData.LowerCase, 1),
// at least one digit character
new CharacterRule(EnglishCharacterData.Digit, 1),
// at least one symbol (special character)
new CharacterRule(EnglishCharacterData.Special, 1),
// no whitespace
new WhitespaceRule()
));
RuleResult result = validator.validate(new PasswordData(password));
if (result.isValid()) {
return true;
}
List<String> messages = validator.getMessages(result);
String messageTemplate = messages.stream().collect(Collectors.joining(","));
context.buildConstraintViolationWithTemplate(messageTemplate)
.addConstraintViolation()
.disableDefaultConstraintViolation();
return false;
}
}
@Documented
@Constraint(validatedBy = PasswordConstraintValidator.class)
@Target( {ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.LOCAL_VARIABLE, ElementType.TYPE_PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
String message() default "Invalid Password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Adding the @ValidPassword annotation for a field in a class works. However when I try adding the annotation to a parameter in a function, the validator is never called/reached.
public void resetUserPassword(Integer userId, @ValidPassword String newPassword) {
}
Also adding the annotation here doesn't work either:
@PostMapping("/user/resetPassword/{id}")
public ResponseEntity<?> resetUserPassword(@PathVariable("userId") Integer userId, @Valid @ValidPassword @RequestBody String newPassword) {
userService.resetUserPassword(userId, newPassword)
return ResponseEntity.ok().build();
}
I don't think I am missing any dependencies, so I'm not sure where the problem is.
CodePudding user response:
You need to add @Validated
annotation on the controller class or another class, where you want to validate method parameter with your custom validation.
There was an explanation in spring-boot 2.1.x documentation about this kind of method-level validation, but I couldn't find it in current 2.7.x docs.
In general it's a spring-framework feature, that can be found here. In a non-boot project you'll need to create a bean of type MethodValidationPostProcessor
manually, but spring-boot auto-configurates this bean for you - the autoconfiguration can be found in ValidationAutoConfiguration
class.
According to java-docs of MethodValidationPostProcessor
, target classes with JSR-303 constraint annotated methods need to be annotated with Spring's @Validated
annotation at the type level, for their methods to be searched for inline constraint annotations. Validation groups can be specified through @Validated
as well. By default, JSR-303 will validate against its default group only.
CodePudding user response:
The annotation @Validated defined at class-level annotation is necessary to trigger method validation for a specific bean to begin with.
Also in other words
The @Validated annotation is a class-level annotation that we can use to tell Spring to validate parameters that are passed into a method of the annotated class.
Refer this link https://github.com/spring-projects/spring-framework/issues/11039 to find out the origin for @Validated
Usage:
As you have the below method with your custom annotation @ValidPassword
with your @Valid
@PostMapping("/user/resetPassword/{id}")
public ResponseEntity<?> resetUserPassword(@PathVariable("userId") Integer userId, @Valid @ValidPassword @RequestBody String newPassword) {
userService.resetUserPassword(userId, newPassword)
return ResponseEntity.ok().build();
}
@Valid
It is used for enabling the whole object validation As you can see in the below example the @NotNull
@Size
and @NotBlank
will be invoked to validate the user input or supplied values present in the object.
Eg:
public class DummyUser{
@NotNull
@Size(min =8)
private String password;
@NotBlank
private String username;
}
@Validated
But, As per your case you want your custom validation to be invoked on the method parameter , Hence you need to provide the hint to spring to invoke the custom validation. So, to do that you have declare the @Validated
annotation at class level in your controller.
So these are the reasons for your validation to start working after you have annotated your controller class with @Validated
annotation.