Spring Security 5 : There is no PasswordEncoder mapped for the id "null"
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.
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:
- 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));