3

I use Spring 4 and Hibernate 5

I have User class with password field with custom validator.

I need to validate it while form binding to be 8 characters long and include lowercase and uppercase letters, and numbers.

When user enters password it is valid, but it is not valid when i encode it.

So is there a way to make my custom validation annotation to be ignored on persist?

I know I can make different field for unencrypted password, or make data transfer object, validate it and then pasrse it's data to User. But I am interested in possibility of annotation parametrization.

@Entity
@Table(name = "user")
public class User { 
//other fields

@NotNull
@NotEmpty
@ValidPassword
@Column(name = "password", nullable = false, length = 60)
private String password;
//getters and setters 
}

My validator

@Target({ TYPE, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
@Documented
public @interface ValidPassword {
String message() default "Password is too short! Must be 8 digits and    include lowercase, uppercase letters and numbers.";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

and

public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {

private Pattern pattern;
private Matcher matcher;
private static final String PATTERN = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,})";

@Override
public void initialize(ValidPassword constraintAnnotation) {
}

@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
    return (validate(password));
}

private boolean validate(String password) {
    pattern = Pattern.compile(PATTERN);
    matcher = pattern.matcher(password);
    return matcher.matches();
}
}

Controller method

@RequestMapping(value = "/registeruser", method = RequestMethod.POST)
public String registerUser(@ModelAttribute("user") @Valid User user, BindingResult result, Model model) {
    if (result.hasErrors()) {
        model.addAttribute("errorSummary", result.getFieldErrors().stream()
                .map(e -> e.getField() + " error - " + e.getDefaultMessage() + " ").collect(Collectors.toList()));
        model.addAttribute("user", user);

    } else {
        User registered = null;
        registered = createUserAccount(user, result);

        if (registered == null) {
            model.addAttribute("errorSummary", "User with this email already registered!");
            model.addAttribute("user", user);
            return "registration";
        }
        model.addAttribute("flashMessage", "User registered successfully!");
    }

    return "registration";
}

UserService implementation method(where I encode my password)

@Transactional
@Override
public User registerNewUserAccount(User user) throws EmailExistsException {

    if (emailExist(user.getEmail())) {
        throw new EmailExistsException("There is an account with that email address:" + user.getEmail());
    }

    if (user.getPassword() == null) {
        user.setPassword(new BigInteger(130, new SecureRandom()).toString(32));
        System.out.println("+++++++++++++++" + user.getPassword());
    }
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    user.setUserRole(new HashSet<UserRole>(1));
    user.getUserRole().add(new UserRole(user, Constants.RoleType.USER.name()));
    save(user);
    return user;
}
Svitlana Onish
  • 284
  • 3
  • 14
  • Waht does "is not valid when i encode it", exactly and where in the code are you getting that error? – zaph Jun 14 '17 at 13:52
  • I encountered such a scenario a month ago, when I wanted to run validations on persist only. See: https://stackoverflow.com/questions/43964065/hibernate-validations-on-save-insert-only/43984952#43984952. You would have to set the validation to be performed during "javax.persistence.validation.group.pre-update" (and possibly "javax.persistence.validation.group.pre-remove"). See full documentation here: https://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/additionalmodules.html – Tomer A Jun 14 '17 at 15:58
  • @zaph when user enters correct password is has 8 digits and so on, when I encode it with `passwordEncoder.encode(user.getPassword())` it is mush longer now (for example: $2a$10$3/ufbdU3qMnn7e3IQ7hkjubrdRJrD7kKieAqflKEDvZ8b7DY8eUcu), so validation fails – Svitlana Onish Jun 15 '17 at 08:28
  • @TomerA I need to make validation on form binding, and not in persist so opposite to what you wanted – Svitlana Onish Jun 15 '17 at 08:31
  • The password is verified by applying the same function to it and verifying that the result is the same as the DB entry. She of the pre-amble `$2a$10$` is information on how it was generated. – zaph Jun 16 '17 at 14:47

1 Answers1

1

By default validation will happen for all constraints. Or you can specify Grouping Constraints

You can create a group by creating an interface:

interface FormValidationGroup{}

And annotate the password field like this:

@ValidPassword(groups = FormValidationGroup.class)
private String password;

Documentation for Custom Constraints annotations where groups parameter is mentioned.

Hibernate Validator should now ignore the password field unless you specify the group for validation. In order to specify a group for validation of a parameter of a Spring MVC handler method, use the Validated annotation instead of Valid. E.g.:

String registerUser(@ModelAttribute @Validated(FormValidationGroup.class) User user,
        BindingResult result, Model model) {
    if (result.hasErrors()) {
Manos Nikolaidis
  • 21,608
  • 12
  • 74
  • 82