I have the User entity:
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Pattern(regexp = "[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}", message = "Must be a valid e-mail address")
@Column(name = "email", nullable = false, unique = true)
private String email;
@Pattern(regexp = "(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{6,}",
message = "Must be minimum 6 characters, at least one letter and one number")
@Column(name = "password", nullable = false)
private String password;
}
On the web app, the user can register so I receive in the Controller username and password using @Validated
:
public String create(@Validated @ModelAttribute("user") User user, BindingResult result) throws EntityNotFoundException {
user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword())); // <-- hashing
userService.create(user);
return "redirect:/profile";
}
As I understand, Spring goes here to the User entity and checks that @Pattern
annotation. Then, if the password matches the pattern I need to hash it. But after hashing I tried to save the User in DB with the method like this:
@Override
public User create(User user) {
return userRepository.save(user); // JPA repo
}
but Spring again goes to the User entity to check @Pattern
annotation and it doesn't match. So it doesn't create a new user in DB. How can I fix this? Any classic solutions? Will be thankful for any advice. I want to save in the DB user with a hashed password without deleting @Pattern
in the Entity.
I think the better way to solve this is to separate password validation logic from the entity itself. You can have a DTO (Data Transfer Object) in your controller responsible for validating the rules of password requirements and after implementing password salt you can convert it to the entity before passing it to the service responsible for creating the object in the database.
The DTO must be like this:
public class UserDTO {
private long id;
@Pattern(regexp = "[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}", message = "Must be a valid e-mail address")
private String email;
@Pattern(regexp = "(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{6,}",
message = "Must be minimum 6 characters, at least one letter and one number")
private String password;
}
Your entity will remove at least the validation for the password rule:
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Pattern(regexp = "[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}", message = "Must be a valid e-mail address")
@Column(name = "email", nullable = false, unique = true)
private String email;
@NotNull
@Column(name = "password", nullable = false)
private String password;
}
Of course, you can also add @Valid
to the service's created method to validate another expression before sending it to database validation.
Your controller method should look like this:
public String create(@Valid @ModelAttribute("user") UserDTO user, BindingResult result) throws EntityNotFoundException {
User userEntity = new User();
userEntity.setId(user.getId());
userEntity.getEmail(user.getEmail());
userEntity.setPassword(new BCryptPasswordEncoder().encode(user.getPassword())); // <-- hashing
userService.create(userEntity);
return "redirect:/profile";
}
You can also use some libraries like MapStruct to reduce the boilerplate of mapping those values between similar objects like that.