6

I have a spring data rest custom user repository in which password need to be encrypted using BCCrypt. From the UI I am sending the plain password, I want to know where to convert the plain password into BCCrypt hash before hibernate creates user in DB. Should I use before save interceptor and hash the password? Or is there any way I can tell spring to use password encoder?

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
Vijay Muvva
  • 1,063
  • 1
  • 17
  • 31
  • Does this answer your question? [Password encoding with Spring Data REST](https://stackoverflow.com/questions/30260582/password-encoding-with-spring-data-rest) – lcnicolau Jun 26 '22 at 17:02

2 Answers2

13

The way to intercept inserts in Spring Data Rest is using an event handler.

NOTE: This code won't work with PATCH operations that don't include the password field.

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

  @Autowired 
  private BCryptPasswordEncoder passwordEncoder;

  @Autowired 
  private UserRepository userRepository;

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

  @HandleBeforeSave
  public void handleUserUpdate(User user) {
    if (user.getPassword() == null || user.getPassword().equals("")) {
        //keeps the last password
        User storedUser = userRepository.getOne(user.getId());
        user.setPassword(storedUser.getPassword());
    }
    else {
        //password change request
        user.setPassword(passwordEncoder.encode(user.getPassword()));
    }
  }
}
Javier Alvarez
  • 1,409
  • 2
  • 15
  • 33
  • What when the user exists, and is a password update? – David Riccitelli Jun 08 '16 at 13:29
  • @DavidRiccitelli Then you should implement a similar method annotated with "@HandleBeforeSave" – Javier Alvarez Jun 13 '16 at 14:26
  • It's not feasible, because if I send a PATCH request w/o the password, the User instance will contain the encrypted password (loaded from the database) and you would encrypt it twice. – David Riccitelli Jun 13 '16 at 14:54
  • 3
    HandleBeforeSave is called only with updates (PATCH and PUT) and not with inserts (POST), so you can do a different implementations. I updated my response with an implementation of that method. – Javier Alvarez Jun 15 '16 at 14:36
  • The implementation is not right, because Spring Data REST on PATCH operations (w/o the password set) will pass a User instance with the password property populated from the database. Therefore you'll end up encrypting an already encrypted password. – David Riccitelli Jun 15 '16 at 14:51
  • 1
    That's true. I ended using always PUT, because Spring Data Rest PATCH is not very RESTful standard and I had other issues with it. By the way, you can send an empty password in PATCH operations always (except password change requests), so it won't be populated from database. – Javier Alvarez Jun 16 '16 at 08:51
  • 1
    I am sorry but I don't think this can be called a solution. You're basically inhibiting anyone else from using your APIs. The problem is *not* Spring Data REST but the solution you're proposing. You should use a custom JsonDeserializer on the password field and encrypt the incoming password when the JSON is deserialized, see http://stackoverflow.com/questions/30260582/password-encoding-with-spring-data-rest/30723658 – David Riccitelli Jun 16 '16 at 10:51
  • Ok, I admit that solution it's ok for the general case. In my case, I removed PATCH verb from my users endpoint (and other endpoints) because the implementation of it in Spring data rest gives is not standard and problematic (recommeded read: http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/). So I shouldn't call it "inhibiting anyone from using my APIs". – Javier Alvarez Jun 16 '16 at 11:23
  • It's good that you added a notice on top of the answer. Have a good one! – David Riccitelli Jun 16 '16 at 11:35
2

You need to do it in your Registration-Service, like the following:

    @Autowired 
    private BCryptPasswordEncoder passwordEncoder;
    ...
    public void registerUser(final User user)
    {
        final String encodedPassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encodedPassword);
        userRepo.save(user);
    }

The password-encoder i refer you, is the org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder this encoder automatically generate a salt for you.

Manu Zi
  • 2,300
  • 6
  • 33
  • 67
  • 6
    If you use Spring-Data-Rest you won't hit any service layer? How would you be able to insert this Service before the Repository when using the implicit Spring-Data-Rest controllers? – Kevin Wittek Jan 19 '16 at 21:39
  • @ManuZi have you been able to resolve this? I'm aware that I can implement a service layer that encodes and delegates the persistence job to the repository, but I'm looking for a more elegant and lightweight solution that uses only implicit JPA repositories. Can we somehow configure JPA to do that? – KareemJ Jan 05 '21 at 10:09
  • I think there is no other "elegant" way to solve this issue. – Manu Zi Jan 11 '21 at 10:44