Summary

Provide a Migration from 4.x to 5.x guide

PasswordEncoder

In the meantime, please refer to [PasswordEncoder javadoc](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/password/PasswordEncoder.html]. It refers to other implementations that will discuss migration strategies. For example, you can refer to MessageDigestPasswordEncoder javadoc if you used DigestPasswordEncoder.

Comment From: cemo

I have just start to migrate our existing code base to 5.x and hit some problems. Our users had their salt values on their user table and currently I can see that salt completely has disappeared. Also PasswordEncoder has gone. DaoAuthenticationProvider does not include a salt source and this might cause us to duplicate previous codes into our code base by extending DaoAuthenticationProvider. What is the best way to upgrade security configurations who are using salt source on user table on db to 5.x currently? I think this is a good addition for documentation as well.

Comment From: cemo

public class SaltedDaoAuthenticationProvider extends DaoAuthenticationProvider {

  static class SaltedUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken{

    private Subject subject;

    SaltedUsernamePasswordAuthenticationToken(Object principal, Object credentials,
                                              Collection<? extends GrantedAuthority> authorities,
                                              Subject subject) {
      super(principal, credentials, authorities);
      this.subject = subject;
    }

    @Override
    public Object getCredentials() {
      String salt = subject.getIdentity().getSalt();
      SaltedSha256PasswordEncoder encoder = new SaltedSha256PasswordEncoder();
      return encoder.encodePassword(super.getCredentials().toString(), salt);
    }
  }

  protected void additionalAuthenticationChecks(UserDetails details,  UsernamePasswordAuthenticationToken auth)
      throws AuthenticationException {

    if(details instanceof MyDetails){
      auth = new SaltedUsernamePasswordAuthenticationToken(auth.getPrincipal(),
                                                           auth.getCredentials(),
                                                           auth.getAuthorities(),
                                                           (MyDetails) details);
    }

    super.additionalAuthenticationChecks(details, auth);
  }
}

I have come up with such an implementation and I am fine now.

Comment From: rwinch

@cemo You need a data migration to migrate the salt to be part of the password to use the existing PasswordEncoder this is outlined in MessageDigestPasswordEncoder

Comment From: rodrigorodrigues

@cemo You need a data migration to migrate the salt to be part of the password to use the existing PasswordEncoder this is outlined in MessageDigestPasswordEncoder

Hi @rwinch, I'm trying to migrate from 3 o 5 but completely lost with the configuration, could you show an example please?

Spring 3

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception   {
    auth.userDetailsService(userDetailsService);
    auth.authenticationProvider(authenticationProvider());
}

@Bean
public PasswordEncoder passwordEncoder() {
    return new ShaPasswordEncoder(256);
}

@Bean
public DaoAuthenticationProvider authenticationProvider() throws Exception {
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(userDetailsService);
    authenticationProvider.setPasswordEncoder(passwordEncoder());
    ReflectionSaltSource saltSource = saltSource();
    authenticationProvider.setSaltSource(saltSource);
    return authenticationProvider;
}

@Bean
public ReflectionSaltSource saltSource() {
    ReflectionSaltSource saltSource = new ReflectionSaltSource();
    saltSource.setUserPropertyToUse("salt");
    return saltSource;
}
}

The salt and password are saved on database in different fields.

Spring 5

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception   {
    auth.userDetailsService(userDetailsService);
    auth.authenticationProvider(authenticationProvider());
}

@Bean
public DaoAuthenticationProvider authenticationProvider() throws Exception {
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(userDetailsService);
    authenticationProvider.setPasswordEncoder(passwordEncoder());
    return authenticationProvider;
}

@Bean
public PasswordEncoder passwordEncoder() {
    return new MessageDigestPasswordEncoder();// What should be in the parameter or should I use StandardPasswordEncoder instead?
}
}

For the data migration if I save {salt}+password would be enough or do I need to do something else?

Thanks.

Comment From: rwinch

For the data migration if I save {salt}+password would be enough or do I need to do something else?

Yes first migrate to using {salt}+password and the new version of the PasswordEncoder. Once you have done that, upgrade to Security 5.

If you are still having issues and can put together a minimal sample, I'd be happy to help with the sample.

Comment From: rodrigorodrigues

Hi @rwinch ,

I have migrated the passwords and upgraded to Security 5 now got the following IllegalArgumentException: Detected a Non-hex character at 1 or 2 position at DaoAuthenticationProvider line 86, if you could help me on that I would really appreciate it, thanks

    @Bean
    public PasswordEncoder passwordEncoder() {
    PasswordEncoder defaultEncoder = new StandardPasswordEncoder();
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    encoders.put("bcrypt", new BCryptPasswordEncoder());

    DelegatingPasswordEncoder passworEncoder = new DelegatingPasswordEncoder(
            "bcrypt", encoders);
    passworEncoder.setDefaultPasswordEncoderForMatches(defaultEncoder);

    return passworEncoder;
    }

Exception

java.lang.IllegalArgumentException: Detected a Non-hex character at 1 or 2 position
    at org.springframework.security.crypto.codec.Hex.decode(Hex.java:62) ~[spring-security-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.security.crypto.password.StandardPasswordEncoder.decode(StandardPasswordEncoder.java:105) ~[spring-security-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.security.crypto.password.StandardPasswordEncoder.matches(StandardPasswordEncoder.java:80) ~[spring-security-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:198) ~[spring-security-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
    at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:86) ~[spring-security-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]

Comment From: rwinch

By invoking passworEncoder.setDefaultPasswordEncoderForMatches(defaultEncoder); any password that is not prefixed with a valid id will delegate to StandardPasswordEncoder with the entire password. So if you have something like {not-mapped}asdfsadfsd then {not-mapped}asdfsadfsd will be passed to StandardPasswordEncoder.

Comment From: rwinch

If you have a password that is {sha256}asdfsadfsd then the entire thing is going to be passed into StandardPasswordEncoder which will fail with an error like that. Instead, you should add StandardPasswordEncoder in the encoders map and avoid using setDefaultPasswordEncoderForMatches. If that doesn't help, please create a question on StackOverflow that contains the encoded password value you are having trouble with.

PasswordEncoder defaultEncoder = new StandardPasswordEncoder();
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder());
encoders.put("sha256", new defaultEncoder);

return new DelegatingPasswordEncoder(
        "bcrypt", encoders);

We try and discourage asking for help on GitHub as this makes it harder for users to find. In the future, please ask on StackOverflow. If you don't get help there, I don't mind pinging via GitHub issues with a link to StackOverflow.

Comment From: rodrigorodrigues

Hi @rwinch thanks for your help appreciated, I solved the issue using new MessageDigestPasswordEncoder("SHA-256") instead new StandardPasswordEncoder().

Comment From: aschatten

Related issue that causes the migration to fail if Spring Security OAuth lib is used and it saves Spring Security objects in a database: https://github.com/spring-projects/spring-security-oauth/issues/662

Comment From: rwinch

This can be closed as no longer relevant