9

I'm using Spring Boot Data REST to persist my User entities

@Entity
public class User {

    @Id
    @GeneratedValue
    private long id;

    @NotEmpty
    private String firstName;

    @NotEmpty
    private String lastName;

    @NotEmpty
    private String email;

    @Size(min = 5, max = 20)
    private String password;

    // getters and setters
}

using the repository:

public interface UserRepository extends CrudRepository<User, Long> {}

What I want to do is validate the POSTed users first:

@Configuration
public class CustomRestConfiguration extends SpringBootRepositoryRestMvcConfiguration {

    @Autowired
    private Validator validator;

    @Override
    protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
        validatingListener.addValidator("beforeCreate", validator);
    }

}

and only later hash the user's password before storing it in the DB:

@Component
@RepositoryEventHandler(User.class)
public class UserRepositoryEventHandler {

    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @HandleBeforeCreate
    public void handleUserCreate(User user) {
         user.setPassword(passwordEncoder.encode(user.getPassword()));
    }
}

As it turns out though, validation is performed after the password hashing and as a result it fails due to the hashed password being too long.

Is there any way to instruct Spring to perform validation first and only then hash the password? I know I could write a controller myself and specify everything in a fine-grained manner, but I'd rather leave it as my last resort.

Wojtek
  • 2,514
  • 5
  • 26
  • 31
  • 1
    What version of Spring Boot/Data REST are you using? I just tested this on my app and when I put breakpoint in the Repo handler and `SizeValidatorForCharSequence`, the one in the validator is hit before the handler, so for me it works as expected. I'm using Spring Boot 1.2.5 – Bohuslav Burghardt Oct 11 '15 at 15:03
  • @BohuslavBurghardt I'm on Spring Boot `1.2.6.RELEASE` and it manages all other dependency versions. I don't really know how to put a breakpoint on the validator, as I autowire the one (I suppose) created by JPA. It happens somewhere behind the scenes. Will try to do that though – Wojtek Oct 11 '15 at 15:45
  • @Wojtek, Is there any other hook using which I can get control after successful bean validation (before it persist entity)? – masT Jun 29 '17 at 10:17
  • @masT Isn't this one exactly what you would need? It gets invoked after validation and before persisting. Or do you mean after entity validation by JPA? I haven't worked with Spring or JPA for quite some time now, so I'm not really able to answer your question though. – Wojtek Jun 29 '17 at 10:42
  • @Wojtek, in my case, it's just not the validations I need to perform but to supply values for some other attributes (to be fetched from another repository), so looking for any hook post-validations to do this processing. – masT Jun 29 '17 at 13:11
  • @masT I see. Well, I cannot really help you here, unfortunately. Haven't played with Spring for a long time now. – Wojtek Jun 29 '17 at 17:51

1 Answers1

3

As I investigated in the debugger it turned out that an incoming entity is processed in the following order:

  1. Spring performs bean validation when deserializing JSON in SpringValidatorAdapter::validate. The password here is in plain text.
  2. @HandleBeforeCreate is invoked and the password is hashed.
  3. JPA performs entity validation before saving it in the DB. The password here is already hashed and validation fails. In my case (Hibernate implementation of JPA) the validation was performed in BeanValidationEventListener::validate.

Solution 1 (full validation in both phases)

One solution I found was to relax the constraint on the password field by just using @NotEmpty (so that both validation phases passed and still the incoming JSON was checked for emptiness/nullity) and perform the size validation of the raw password in @HandleBeforeCreate (and throw appropriate exception from there if needed).

The problem with this solution is that it required me to write my own exception handler. To keep up with the high standards set by Spring Data REST with respect to error response body, I would have to write lots of code for this one simple case. The way to do this is described here.

Solution 2 (Spring bean validation without JPA entity validation)

As hinted by Bohuslav Burghardt, it is possible to disable the second validation phase done by JPA. This way you can keep the min and max constrains and at the same time avoid writing any additional code. As always it's a trade-off between simplicity and safety. The way to disable JPA is described here.

Solution 3 (preserving only min password length constraint)

Another solution, at least valid in my case, was to leave the max password length unbounded. This way in the first validation phase the password was checked whether it wasn't too short and in the second phase it effectively validated every time (because encrypted password was already long enough).

The only caveat to this solution is that @Size(min = 5) does not seem to check for nullity so I had to add @NotNull to handle this case. All in all the field is annotated as:

@NotNull
@Size(min = 5)
private String password;
Community
  • 1
  • 1
Wojtek
  • 2,514
  • 5
  • 26
  • 31
  • 1
    Interesting reading. The reason it worked for me is probably because I have Hibernate validation disabled via application.properties (`spring.jpa.properties.javax.persistence.validation.mode=none`). You can do that too, then only the validation during deserialization at step 1 will be performed. – Bohuslav Burghardt Oct 11 '15 at 18:13
  • Yes, I read about it but I didn't really want to resign from all of JPA validation goodness :) Will add it as another possible solution. Thank you for your help Bohuslav! – Wojtek Oct 11 '15 at 18:28
  • 1
    how a about validating an String with Regex? – Mahdi Feb 25 '17 at 07:13