curl -X GET http://localhost:8080/api/client -H 'Authorization: Basic Y29hY2hpbmdfd2JjppVU5EeTlVdFg4eTQzTkIxMmg=' -H 'Content-Type: application/json'
There is no PasswordEncoder mapped for the id
Upasana | July 25, 2020 | 5 min read | 4,247 views | Spring Boot 2
After migration to Spring Boot 2, you may encounter the below error when you try to hit a protected endpoint with a username and password.
-
Spring Boot 2.3.2.RELEASE
-
Spring Security 5.3.3.RELEASE
{
"timestamp": "2019-07-09T09:02:51.637+0000",
"status": 500,
"error": "Internal Server Error",
"message": "There is no PasswordEncoder mapped for the id \"null\"",
"path": "/api/wbc/client"
}
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)
Here is the Spring Security Java Configuration:
@Configuration
@Order(1)
public class WbcSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password1").roles("USER")
.and()
.withUser("admin").password("password2").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.httpBasic()
.and()
.csrf()
.disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Solutions
Prior to Spring Security 5.0, the default PasswordEncoder was NoOpPasswordEncoder
which required plain text passwords but is insecure. Spring Security 5.x onwards, the default PasswordEncoder is DelegatingPasswordEncoder
, which requires a Password Storage Format. You can find more details on migrating to Spring Security 5 this blog Migrating to Spring Security 5
Password Storage Format
The general format for a password is:
{id}encodedPassword
where:
- "id"
-
is an identifier used to look up which PasswordEncoder should be used.
- "encodedPassword"
-
is the original encoded password for the selected PasswordEncoder.
Most commonly used PasswordEncoders with their id’s are:
-
"noop" which uses plain text
NoOpPasswordEncoder
-
"bcrypt" which uses `BCryptPasswordEncoder'
-
"scrypt" which uses
SCryptPasswordEncoder
-
"pbkdf2" which uses 'Pbkdf2PasswordEncoder'
-
"sha256" which uses
StandardPasswordEncoder
Example of a Password that is encoded using bcrypt is:
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
DelegatingPasswordEncoder
solves many of the challenges:
-
Allows validating passwords in modern and legacy formats.
-
Allows for upgrading to a newer encoding in the future.
-
Ensures that passwords are encoded using the current password storage recommendations.
We can easily create an instance of DelegatingPasswordEncoder
using the below code:
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
or infact, you can create a bean for the same:
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Solution 1. Add password storage format
We can add password storage format, which is {noop} for plain text passwords.
The new WebSecurityConfig will look like this:
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
@Configuration
@Order(1)
public class WbcSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
withUser("user").password("{noop}password1").roles("USER")
.and()
.withUser("admin").password("{noop}password2").roles("ADMIN"); (1)
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().hasRole("USER_ROLE")
.and()
.httpBasic()
.and()
.csrf()
.disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
1 | Here we are telling Spring Security that NoOpPasswordEncoder shall be used to decode the password. We shall use {bcrypt} instead which is more secure way of storing password in source code files. |
Now everything shall work fine.
If you want to programmatically generate passwords in storage format, you can use the below code snippet:
User user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("user")
.build();
System.out.println(user.getPassword());
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
Using Spring Boot CLI
Optionally, you can use Spring Boot CLI to quickly encode your password into storage format. Follow the instructions below to use Spring Boot CLI.
curl -s "https://get.sdkman.io" | bash
If everything goes good, check if SDKMAN is correctly installed:
sdk --version
Now install spring boot cli
sdk install springboot
Check the version
spring --version
Encrypt a given plain password:
spring encodepassword foo
{bcrypt}$2a$10$g08xa7qe52RUj8PgS3s4U.2SPFuKyuxKo8d6eEaQB4s04TKbkhe.C
Solution 2. Create a UserDetailsService Bean (not preferred)
We can use User.withDefaultPasswordEncoder()
for UserDetailsService
that will automatically take care of hashing the password in required format.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class WbcSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService() {
User.UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password1").roles("USER").build());
manager.createUser(users.username("admin").password("password2").roles("ADMIN").build());
return manager;
}
}
This does hash the password that is stored, but the passwords are still exposed in memory and in the compiled source code. Therefore, it is still not considered secure for a production environment. For production, you should always hash your passwords externally. |
Solution 2. Switching to older behavior (insecure)
Optionally, we can switch back to NoOpPasswordEncoder by declaring a bean using below code:
@Config
public class SecurityConfig {
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
or, even you can do the same using below code:
@Configuration
@Order(1)
public class WbcSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance()) (1)
.withUser("user").password("{noop}password1").roles("USER")
.and()
.withUser("admin").password("{noop}password2").roles("ADMIN");
}
// rest of the code
}
1 | We are intentionally reverting to the previous behavior of Spring Security |
But both of these are insecure solutions and should not be preferred, as suggested by Spring Docs.
Reverting to NoOpPasswordEncoder is not considered to be secure. You should instead migrate to using DelegatingPasswordEncoder to support secure password encoding.
What is NoOpPasswordEncoder
NoOpPasswordEncoder is a password encoder that does nothing. It has been deprecated now. It is useful for testing where working with plain text passwords is preferred.
This NoOpPasswordEncoder is provided for legacy and testing purposes only and is not considered secure.
Top articles in this category:
- Multi-threading Java Interview Questions for Investment Bank
- What is difference between Primary key and Unique Key
- SQL - Write a query to find customers who have no orders yet
- Difference between getOne and findById in Spring Data JPA?
- Reverse the bits of a number and check if the number is palindrome or not
- Sapient Global Market Java Interview Questions and Coding Exercise
- What is difference between HTTP Redirect and Forward