Spring Security 5 : There is no PasswordEncoder mapped for the id “null”

自古美人都是妖i 提交于 2019-12-17 06:11:29

问题


I am migrating from Spring Boot 1.4.9 to Spring Boot 2.0 and also to Spring Security 5 and I am trying to do authenticate via OAuth 2. But I am getting this error:

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null

From the documentation of Spring Security 5, I get to know that storage format for password is changed.

In my current code I have created my password encoder bean as:

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

However it was giving me below error:

Encoded password does not look like BCrypt

So I update the encoder as per the Spring Security 5 document to:

@Bean
public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

Now if I can see password in database it is storing as

{bcrypt}$2a$10$LoV/3z36G86x6Gn101aekuz3q9d7yfBp3jFn7dzNN/AL5630FyUQ

With that 1st error gone and now when I am trying to do authentication I am getting below error:

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null

To solve this issue I tried all the below questions from Stackoverflow:

  • Spring Boot PasswordEncoder Error

  • Spring Oauth2. Password encoder is not set in DaoAuthenticationProvider

Here is a question similar to mine but not answerd:

  • Spring Security 5 - Password Migration

NOTE: I am already storing encrypted password in database so no need to encode again in UserDetailsService.

In the Spring security 5 documentation they suggested you can handle this exception using:

DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)

If this is the fix then where should I put it? I have tried to put it in PasswordEncoder bean like below but it wasn't working:

DelegatingPasswordEncoder def = new DelegatingPasswordEncoder(idForEncode, encoders);
def.setDefaultPasswordEncoderForMatches(passwordEncoder);

MyWebSecurity class

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {

        web
                .ignoring()
                .antMatchers(HttpMethod.OPTIONS)
                .antMatchers("/api/user/add");
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

MyOauth2 Configuration

@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;


    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new CustomTokenEnhancer();
    }

    @Bean
    public DefaultAccessTokenConverter accessTokenConverter() {
        return new DefaultAccessTokenConverter();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        endpoints
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancer())
                .accessTokenConverter(accessTokenConverter())
                .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()
                .withClient("test")
                .scopes("read", "write")
                .authorities(Roles.ADMIN.name(), Roles.USER.name())
                .authorizedGrantTypes("password", "refresh_token")
                .secret("secret")
                .accessTokenValiditySeconds(1800);
    }
}

Please guide me with this issue. I have spend hours to fix this but not able to fix.


回答1:


When you are configuring the ClientDetailsServiceConfigurer, you have to also apply the new password storage format to the client secret.

.secret("{noop}secret")



回答2:


For anyone facing the same issue and not in need of a secure solution - for testing and debugging mainly - in memory users can still be configured.

This is just for playing around - no real world scenario.

The approach used below is deprecated.

This is where I got it from:

  • source
  • reference, also mentioned in the source above

Within your WebSecurityConfigurerAdapter add the following:

@SuppressWarnings("deprecation")
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}

Here, obviously, passwords are hashed, but still are available in memory.


Of course, you could also use a real PasswordEncoder like BCryptPasswordEncoder and prefix the password with the correct id:

// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));



回答3:


Add .password("{noop}password") to Security config file.

For example :

auth.inMemoryAuthentication()
        .withUser("admin").roles("ADMIN").password("{noop}password");



回答4:


Whenever Spring stores the password, it puts a prefix of encoder in the encoded passwords like bcrypt, scrypt, pbkdf2 etc. so that when it is time to decode the password, it can use appropriate encoder to decode. if there is no prefix in the encoded password it uses defaultPasswordEncoderForMatches. You can view DelegatingPasswordEncoder.class's matches method to see how it works. so basically we need to set defaultPasswordEncoderForMatches by the following lines.

@Bean(name="myPasswordEncoder")
public PasswordEncoder getPasswordEncoder() {
        DelegatingPasswordEncoder delPasswordEncoder=  (DelegatingPasswordEncoder)PasswordEncoderFactories.createDelegatingPasswordEncoder();
        BCryptPasswordEncoder bcryptPasswordEncoder =new BCryptPasswordEncoder();
    delPasswordEncoder.setDefaultPasswordEncoderForMatches(bcryptPasswordEncoder);
    return delPasswordEncoder;      
}

Now, you might also have to provide this encoder with DefaultPasswordEncoderForMatches to your authentication provider also. I did that with below lines in my config classes.

@Bean
    @Autowired  
    public DaoAuthenticationProvider getDaoAuthenticationProvider(@Qualifier("myPasswordEncoder") PasswordEncoder passwordEncoder, UserDetailsService userDetailsServiceJDBC) {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        daoAuthenticationProvider.setUserDetailsService(userDetailsServiceJDBC);
        return daoAuthenticationProvider;
    }



回答5:


Regarding

Encoded password does not look like BCrypt

In my case there was a mismatch in BCryptPasswordEncoder strength used by default constructor (10) as pwd hash was generated with strength 4. So I've set strength explicit.

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(4);
}

also my Spring Security version is 5.1.6 and it perfectly works with BCryptPasswordEncoder



来源:https://stackoverflow.com/questions/49654143/spring-security-5-there-is-no-passwordencoder-mapped-for-the-id-null

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!