Spring Security 5 : There is no PasswordEncoder mapped for the id "null"
Solution 1
When you are configuring the ClientDetailsServiceConfigurer
, you have to also apply the new password storage format to the client secret.
.secret("{noop}secret")
Solution 2
Add .password("{noop}password")
to Security config file.
For example :
auth.inMemoryAuthentication()
.withUser("admin").roles("ADMIN").password("{noop}password");
Solution 3
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));
Solution 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;
}
Solution 5
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
Comments
-
Jimmy over 2 years
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.
-
olivmir about 6 yearssee also Password Matching and Password Storage Format in Spring Security 5.0.0.RC1 Released remarks
-
Jimmy about 6 yearsHi, actually we can not use NoOpPasswordEncoder. As it is deprecated in new spring-security version. And as per the spring security documentation (spring.io/blog/2017/11/01/…) 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.
-
rocksteady about 6 yearsYes, it is deprecated. My source tells so, also. If you just use
NoOpPasswordEncoder
- withoutBCryptPasswordEncoder
- it works. I use spring boot2.0.1.RELEASE
. -
rocksteady about 6 yearsBut, as I've mentioned in the answer, this is no production scenario at all.
-
Jimmy about 6 yearsI am not getting why we should use a deprecated class even for Testing purpose?
-
rocksteady about 6 yearsOk, 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. -
Troy Young over 5 yearsalso can add the
secret(passwordEncoder.encode("secret"))
by using the default password encoder. -
Osama Abdulsattar over 5 yearsThe answer helped me with a testing scenario, thanks
-
rocksteady about 5 yearsThat's why I said so at the beginning of the answer.
-
f.khantsis about 5 yearswhat if I don't use a secret?
-
Indrajeet Gour over 4 yearscan you tell me why you added {noop}?
-
Sailokesh Aithagoni over 4 yearsJust want to use plain pass word...! Like no operation to be performed on that! I think so! ;)
-
Jimmy almost 4 yearsThat 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.
-
bzhu over 3 yearsalso: auth.inMemoryAuthentication() .withUser("admin").roles("ADMIN").password("{noop}password");
-
Sebi over 2 years@bzhu what if I don't want to hardcode the future users that I don't know will register?
-
Pierre C over 2 yearsCorrect link to the documentation.