2

I am currently coding a loadUserByUsername method in a UserDetailsServiceImpl java class similarily found in many springboot tutorials. The problem is in the last line

return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), 
grantedAuthorities);

According to the spring security documentation, user.getPassword() should return a string, but I am using bcrypt and MySQL, so when I store the password, I store it in Mysql as binary(60) and when I read it into the user class from the database it is read into a

   @Entity
   @Data
   @AllArgsConstructor
   @NoArgsConstructor
   public class users {
    
    @Id
    private String email;
    private long phone_number;
    private String first_name;
    private String last_name;
    private byte[] password;
    private int gender;
        
}

field in my user class. If i convert it into a string Ive read that it messes up the password, but if I dont then the function doesnt work as I'm passing in a byte[] instead of a string. How can I keep the security of bcrypt while also maintaining this functionality?

Because in mysql docs it maps BINARY(60) to byte[] here https://dev.mysql.com/doc/ndbapi/en/mccj-using-clusterj-mappings.html

and everyone is saying to store bcrypt as binary(60) here What column type/length should I use for storing a Bcrypt hashed password in a Database?

3 Answers3

3

You can just return the hashed password as a String. No need to return the plain password. In fact no one would expect you to.

There's a lot of Spring magic going on behind the scenes here and there is a default password encoder/decoder, e.g. you can get a password encoder with PasswordEncoderFactories.createDelegatingPasswordEncoder(), which also has more custom options. When a user is logging in, the hashed password that is returned from UserDetails.getPassword() would be compared with the hashed version of the password that the user used to log in.

I'm not overly familiar with mysql datatypes but if you let Spring JPA manage your user entity and it has a password field that is encoded with this PasswordEncoderFactories.createDelegatingPasswordEncoder() and then saved as a String, the data type in the database would just be a varchar or whatever the type is called in MySql. This password encoder uses BCrypt by default but you can also configure it to use different hashing algorithms.

Sebastiaan van den Broek
  • 5,818
  • 7
  • 40
  • 73
  • Are you saying that when I do the read from the database, change the password field in the user DAO class from byte[] to string? Wouldn't this go against the mysql doc about reading data types? https://dev.mysql.com/doc/ndbapi/en/mccj-using-clusterj-mappings.html – David Kostandin Oct 28 '20 at 18:25
  • @DavidKostandin I tried to address your comment in my answer. Not sure why your hashed password would have to be binary in your database. But I'm sure you could convert it to a String either way. – Sebastiaan van den Broek Oct 28 '20 at 18:31
  • https://stackoverflow.com/questions/5881169/what-column-type-length-should-i-use-for-storing-a-bcrypt-hashed-password-in-a-d says to store bcrypt as BINARY(60) and not as VARCHAR – David Kostandin Oct 28 '20 at 18:34
  • @DavidKostandin Seems to be a topic of debate in the comment section, but there's nothing preventing you from storing it as binary either. But you'll have to return the String representation of that binary data in the `getPassword()` method then. That's still the hashed password though. That would just be `String s = new String(bytes, StandardCharsets.UTF_8);`, assuming you also stored it in UTF8 that is. – Sebastiaan van den Broek Oct 28 '20 at 18:40
  • Ah I see, the password encoder is already a string at the start, so me doing this would essentially make me have to convert the string to binary to store into the db, then on retrieval turn it back into a string. This way is too roundabout and I should just store the bcrypt hash directly into the db as a varchar and save myself the trouble. ty for the explanation – David Kostandin Oct 28 '20 at 18:46
0

According to first answer, you must compare hashed password from database with password sent by user using passwordEncoder.matches(passwordFromUser, encodedPassword).

You can create a Bean to easy and pretty inject passwordEncoder into UserDetailsService.

fr3ddie
  • 406
  • 6
  • 17
0

The store bcrypt as binary(60) is argumentative mostly. From my experience i would recommend varchar. Since you create a hash of passwords before saving, it won't be a problem to later compare it with a password that users provide (evidently you have to hash this one too).

You said that:

If i convert it into a string Ive read that it messes up the password

I think this can be avoided by using cast of mysql it self. Though it is circling around that doesn't make sense.

Spring 5 has introduced DelegatingPasswordEncoder. A password encoder that delegates to another PasswordEncoder based upon a prefixed identifier.

For instance you could:

class DefaultPasswordEncoderFactories {
  //the passwords must be of form: {bcrypt}xxx in the db
    static PasswordEncoder createDelegatingPasswordEncoder() {
        String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());

        DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(encodingId, encoders);
        delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder(12));
        return delegatingPasswordEncoder;
    }   
}

For instance this can be used easily in DaoAuthenticationProvider:

private final PasswordEncoder passwordEncoder = DefaultPasswordEncoderFactories.createDelegatingPasswordEncoder();
...
@Bean
public DaoAuthenticationProvider authenticationProvider(){
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(ethUserDetailsService);
    provider.setPasswordEncoder(this.passwordEncoder);
    
    return provider;
}

Check the following link, it might be useful apart from the ones you provided What data type to use for hashed password field and what length?

Aman
  • 1,627
  • 13
  • 19