114

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:

Here is a question similar to mine but not answerd:

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.

dur
  • 15,689
  • 25
  • 79
  • 125
Jimmy
  • 1,719
  • 3
  • 21
  • 33
  • I was little bit more descriptive with the issue. When I shifted from Spring security 4 to 5. I was getting first error and then I solved it by changing my password generator, It start giving me second error. And the error messages are diffrent. 1) Encoded password does not look like BCrypt and 2) java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null. Second issues it I am having currently. – Jimmy Apr 05 '18 at 12:20
  • 3
    I believe the issue is with the client (not the individual user accounts). I, personally, was already encoding the user details, but not the client. Now, users of OAuth2 are expected to encode the *client* secret (as well as user passwords). Specifically, either set the `passwordEncoder` on the `ClientDetailsServiceConfigurer` or prefix the secret with {noop}. Hope that makes sense and helps somebody out. – KellyM Apr 06 '18 at 00:46
  • This issue was resolved for me by running `mvn clean package`. Must've been some issue with caching. – Janac Meena Feb 26 '21 at 19:43

12 Answers12

132

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

.secret("{noop}secret")
dur
  • 15,689
  • 25
  • 79
  • 125
Edwin Diaz
  • 1,693
  • 1
  • 12
  • 16
  • see also [Password Matching](https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-matching) and [Password Storage Format](https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-format) in Spring Security 5.0.0.RC1 Released remarks – olivmir Apr 09 '18 at 12:01
  • 1
    also can add the `secret(passwordEncoder.encode("secret"))` by using the default password encoder. – Troy Young Jan 04 '19 at 08:41
  • 3
    what if I don't use a secret? – f.khantsis Feb 10 '19 at 00:08
  • 4
    also: auth.inMemoryAuthentication() .withUser("admin").roles("ADMIN").password("{noop}password"); – bzhu Sep 01 '20 at 06:44
  • @bzhu what if I don't want to hardcode the future users that I don't know will register? – Sebi Nov 28 '21 at 20:14
53

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

For example :

auth.inMemoryAuthentication()
        .withUser("admin").roles("ADMIN").password("{noop}password");
Cà phê đen
  • 1,883
  • 2
  • 21
  • 20
Loki
  • 1,180
  • 9
  • 13
  • 4
    Just want to use plain pass word...! Like no operation to be performed on that! I think so! ;) – Loki Aug 16 '19 at 10:09
22

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:


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));
rocksteady
  • 2,320
  • 5
  • 24
  • 40
  • 3
    Hi, actually we can not use NoOpPasswordEncoder. As it is deprecated in new spring-security version. And as per the spring security documentation (https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-encoding) even if we are using NoOpPasswordEncoder we must add {noop} as an id to password and secret. I believe this won't justify my question. The main problem with all the solution is that they are not mentioning that you need to add an ID to your secret too. – Jimmy Apr 15 '18 at 14:05
  • 1
    Yes, it is deprecated. My source tells so, also. If you just use `NoOpPasswordEncoder` - without `BCryptPasswordEncoder` - it works. I use spring boot `2.0.1.RELEASE`. – rocksteady Apr 15 '18 at 14:35
  • But, as I've mentioned in the answer, this is no production scenario at all. – rocksteady Apr 15 '18 at 14:36
  • I am not getting why we should use a deprecated class even for Testing purpose? – Jimmy Apr 16 '18 at 06:53
  • Ok, you are completely right. I added this answer for every one who is just playing around and starts diving into the matter using an outdated tutorial, maybe. That's why I also mentioned the `BCryptPasswordEncoder`. I updated my answer. – rocksteady Apr 16 '18 at 07:03
5

The java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" error message arises when upgrading from Spring Security 4 to 5. Please refer to this Baeldung article as for the complete explanation and possible solutions.

Pierre C
  • 2,920
  • 1
  • 35
  • 35
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;
    }
Vikky
  • 1,123
  • 14
  • 16
4

Don't know if this will help anyone. My working WebSecurityConfigurer and OAuth2Config code as below:

OAuth2Config File:

package com.crown.AuthenticationServer.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("crown")
            .secret("{noop}thisissecret")
            .authorizedGrantTypes("refresh_token", "password", "client_credentials")
            .scopes("webclient", "mobileclient");
    }

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

WebSecurityConfigurer:

package com.crown.AuthenticationServer.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;


@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

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

    @Bean
    @Override
    public UserDetailsService userDetailsService() {

        PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

        final User.UserBuilder userBuilder = User.builder().passwordEncoder(encoder::encode);
        UserDetails user = userBuilder
            .username("john.carnell")
            .password("password")
            .roles("USER")
            .build();

        UserDetails admin = userBuilder
            .username("william.woodward")
            .password("password")
            .roles("USER","ADMIN")
            .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

}

Here is the link to the project: springboot-authorization-server-oauth2

CrownWangGuan
  • 83
  • 1
  • 7
3

If you are fetching the username and password from the database, you can use below code to add NoOpPassword instance.

protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   auth.userDetailsService(adm).passwordEncoder(NoOpPasswordEncoder.getInstance());
}

Where adm is a custom user object for my project which has getPassword() and getUsername() methods.

Also remember, to make a custom User POJO, you'll have to implement UserDetails interface and implements all of it's methods.

Hope this helps.

Ashish Singh
  • 399
  • 4
  • 11
2

You can read in the official Spring Security Documentation that for the DelegatingPasswordEncoder the general format for a password is: {id}encodedPassword

Such that id is an identifier used to look up which PasswordEncoder should be used and encodedPassword is the original encoded password for the selected PasswordEncoder. The id must be at the beginning of the password, start with { and end with }. If the id cannot be found, the id will be null. For example, the following might be a list of passwords encoded using different id. All of the original passwords are "password".

Id examples are:

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG {noop}password {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc {scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0

Continuity8
  • 2,403
  • 4
  • 19
  • 34
  • That issue is because we need to have encryption type in client secret as well. As of now password is already appending encryption type on encrypted password. – Jimmy May 08 '20 at 03:45
  • Correct link to the [documentation](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/password/DelegatingPasswordEncoder.html). – Pierre C Dec 06 '21 at 19:59
1

For xml configuration.

<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="admin" password="{noop}1234" authorities="ROLE_ADMIN"/>
        </user-service>
    </authentication-provider>
</authentication-manager>
0

Spring Boot official documentation has provided a solution for this

The easiest way to resolve the error is to switch to explicitly providing the PasswordEncoder that your passwords are encoded with. The easiest way to resolve it is to figure out how your passwords are currently being stored and explicitly provide the correct PasswordEncoder.

If you are migrating from Spring Security 4.2.x you can revert to the previous behavior by exposing a NoOpPasswordEncoder bean.

Alternatively, you can prefix all of your passwords with the correct id and continue to use DelegatingPasswordEncoder. For example, if you are using BCrypt, you would migrate ... more

eli
  • 8,571
  • 4
  • 30
  • 40
0
@Configuration
@EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    private UserDetailsService userDetailsService;
    
    
    @Bean
    public AuthenticationProvider authProvider() {
        
        DaoAuthenticationProvider provider =  new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance());
        
        return provider;
    }

}

adding the below two annotation is fixed that issue
@Configuration @EnableWebSecurity

0

juste add this bean to an annoted @configuration class.

@Configuration
public class BootConfiguration {

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

}

next go to your WebSecurity configuration class and call the password bean add it to authentication provider dont forgot set in your auth manager bean the authentication provider bean and final set the authentication manager bean in the SecurityFilterChain use the example below

    @EnableWebSecurity
    @Configuration
    public class DefaultSecurityConfig {
    
        @Autowire
        private PasswordEncoder passwordEncoder
        
    
        @Bean
        public DaoAuthenticationProvider authenticationProvider() {
                DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
                authenticationProvider.setUserDetailsService(userManager);
                authenticationProvider.setPasswordEncoder(passwordEncoder);
                return authenticationProvider;
            }

       @Bean
        public AuthenticationManager authManager(HttpSecurity http) throws Exception {
            AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); 
authenticationManagerBuilder.authenticationProvider(authenticationProvider();

        return authenticationManagerBuilder.build();
    }


 @Bean
 SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(authorize -> authorize
          
            .anyRequest().authenticated());
    http.authenticationManager(authManager(http));
    http.exceptionHandling(exceptions->exceptions
            .accessDeniedHandler(accessDeniedHandler));
    http.formLogin(formLogin-> formLogin.loginPage("/login")
            .permitAll());
    http.logout(logout-> logout
            .permitAll()
    return http.build();
}


}