IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
This is an in-memory users setup for a Spring Security demo that I'm using since Spring 4:
package spring.websocket.chat.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Security Config.
* Defines spring security beans and configurations.
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder authBuilder) throws Exception {
authBuilder.inMemoryAuthentication().withUser("xavier").password("ProfessorX").roles("ADMIN");
authBuilder.inMemoryAuthentication().withUser("logan").password("Wolverine").roles("USER");
authBuilder.inMemoryAuthentication().withUser("scott").password("Cyclops").roles("USER");
authBuilder.inMemoryAuthentication().withUser("ororo").password("Storm").roles("USER");
}
}
Lately, I upgrading this project to Spring 5, and during login, I encountered this issue:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:250) org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:198) org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$LazyPasswordEncoder.matches(AuthenticationConfiguration.java:312) org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:90) org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:166) org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175) org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:195)
Searching the internet, the solution for this issue is quite simple, and well documented here. Prior to Spring Security 5.0 the default PasswordEncoder
was NoOpPasswordEncoder
which required plain text passwords.
Spring Security 5.0 defaulted to DelegatingPasswordEncoder
which:
- Ensuring that passwords are encoded using the current password storage recommendations
- Allowing for validating passwords in modern and legacy formats
- Allowing for upgrading the encoding in the future
"Password storage recommendations" refer to Password Storage Format, which generally following this format:
{id}encodedPassword
id
is an identifier used to look up which PasswordEncoder
should be used and encodedPassword
is the original encoded password for the selected PasswordEncoder
. Here list of id
for different PasswordEncoder
:
id | PasswordEncoder | Description |
---|---|---|
noop | NoOpPasswordEncoder | plain text password |
bcrypt | BCryptPasswordEncoder | bcrypt is a password hashing function based on the Blowfish cipher |
pbkdf2 | Pbkdf2PasswordEncoder | PBKDF2 (Password-Based Key Derivation Function 2) are key derivation functions with a sliding computational cost, used to reduce vulnerabilities to brute force attacks |
scrypt | SCryptPasswordEncoder | scrypt (pronounced "ess crypt") is a password-based key derivation function (algorithm) that specifically designed to make large-scale custom hardware attacks costly by requiring large amounts of memory |
sha256 | StandardPasswordEncoder | SHA-256 — part of SHA-2 (Secure Hash Algorithm 2) family, is novel hash functions computed with 32-bit words. |
In our case id
should be noop
which refer to NoOpPasswordEncoder
, and the original encoded password is in plain text format. So we need to change it like this:
authBuilder.inMemoryAuthentication().withUser("xavier").password("{noop}ProfessorX").roles("ADMIN");
authBuilder.inMemoryAuthentication().withUser("logan").password("{noop}Wolverine").roles("USER");
authBuilder.inMemoryAuthentication().withUser("scott").password("{noop}Cyclops").roles("USER");
authBuilder.inMemoryAuthentication().withUser("ororo").password("{noop}Storm").roles("USER");
NullPointerException InMemoryUserDetailsManager.updatePassword
After change, I encountered this error when running my web application, after login:
java.lang.NullPointerException org.springframework.security.provisioning.InMemoryUserDetailsManager.updatePassword(InMemoryUserDetailsManager.java:147) org.springframework.security.authentication.dao.DaoAuthenticationProvider.createSuccessAuthentication(DaoAuthenticationProvider.java:135) org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:197) org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174) org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:199)
To "fix" this issue, what I need to do is only upgrade my Spring Security from 5.1.1.RELEASE to 5.2.1.RELEASE. Please refer to https://github.com/spring-projects/spring-security/issues/6103