Migrate Spring Security 4.x (ShaPasswordEncoder and unique salt for each user) to 5.x (BCryptPasswordEncoder)

Issue

I am migrating Spring security from 4.2.16.RELEASE to 5.6.3 version.
The existing project security configuration uses deprecated ShaPasswordEncoder and custom SaltSource for the salt generation. The salt is comprised of application configuration property and user’s uuid:

     <bean id="customAuthenticationProvider" class="com.my.security.CustomUserDetailsAuthenticationProvider">
        <property name="userDetailsService" ref="CustomUserDetailsService" />
        <property name="saltSource" ref="saltSource" />
        <property name="passwordEncoder" ref="passwordEncoder" />
        <property name="servicesURL" value="${custom.services.url}" />
    </bean>
    <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
        <constructor-arg value="256" />
    </bean>
    <bean id="saltSource" class="com.my.security.UserSaltSource">
        <property name="userPropertyToUse" value="CustomId" />
        <property name="systemWideSalt" value="${security.systemwide.salt}" />
    </bean>

The getSalt method:

public Object getSalt(String secondSaltProperty) {
    try {       
        return secondSaltProperty + this.systemWideSalt;
    } catch (Exception exception) {
        throw new AuthenticationServiceException(exception.getMessage(),
                exception);
    }
}

My goal is not only migrate to newer Spring security version but also start using the recommended BCryptPasswordEncoder password encoder.
According to the migration guide, I have to migrate the existing passwords:

The format is intended to work with the DigestPasswordEncoder that was found in the Spring Security core module. However, the passwords will need to be migrated to include any salt with the password since this API provides Salt internally vs making it the responsibility of the user. To migrate passwords from the SaltSource use the following:
String salt = saltSource.getSalt(user);
String s = salt == null ? null : "{" + salt + "}";
String migratedPassword = s + user.getPassword();

Question: The thing that I am not clear about the password migration part is combining the new password storage format {id}encodedPassword, where id is used for selecting corresponding password encoder, and the salt part.
According to the migration guide I have to add {sha256} prefix to the stored passwords, so the DelegatingPasswordEncoder will pick up the proper decoder, but how to deal with the salt part then?

My understanding is the following:
First I have to define the DelegatingPasswordEncoder and if I don’t add the id prefix to the password I have to set the default password encoder using DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches method to pick up the old password encoder.
Next, authentication logic is implemented in the custom AuthenticationProvider service class, in which passwordEncoder is injected and used for password matching check. Before calling the PasswordEncoder.matches(CharSequence rawPassword, String encodedPassword);, I have to add the code that will format the encoded password to match the {salt}password format.

Next step is to add Successful authentication listener that will re-encode the passwords to match the {bcrypt}password format and store it into DB. So on the next user log in attempt the BCryptPasswordEncoder password encoder will be invoked instead.

Once all passwords will be migrated, I will be able to delete the custom SaltSource-wise stuff and the Successful authentication listener.

Pls, correct me if I am wrong or if I am missing something. Thanks.

Solution

There are only 2 things you need to do.

  1. Migrate the passwords to include the salt in the pattern {salt}<password-hash>
  2. Configure the DelegatingPasswordEncoder and set the, properly configured, MessageDigestPasswordEncoder as the default. So if no encoding is specified it will fallback to that one.

If you want to convert the passwords on the fly you could do that with a succes handler and update the record to use BCrypt/SCrypt instead of what you have now.

Answered By – M. Deinum

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published