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 POST
ed 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.
As I investigated in the debugger it turned out that an incoming entity is processed in the following order:
SpringValidatorAdapter::validate
. The password here is in plain text.@HandleBeforeCreate
is invoked and the password is hashed.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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With