Spring Security 5 - There is no PasswordEncoder mapped for the id

Carvia Tech | July 09, 2019 | | 1 views | Spring Boot 2


After migration to Spring Boot 2.1, you may encounter the below error when you try to hit a protected endpoint with username and password.

Environment
  1. Spring Boot 2.1.6.RELEASE

  2. Spring Security 5.1.5.RELEASE

Curl Request
curl -X GET http://localhost:8080/api/client -H 'Authorization: Basic Y29hY2hpbmdfd2JjppVU5EeTlVdFg4eTQzTkIxMmg=' -H 'Content-Type: application/json'
Server Response
{
    "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"
}
Server Error Logs
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:

WebSecurityConfig
@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:

  1. "noop" which uses plain text NoOpPasswordEncoder

  2. "bcrypt" which uses `BCryptPasswordEncoder'

  3. "scrypt" which uses SCryptPasswordEncoder

  4. "pbkdf2" which uses 'Pbkdf2PasswordEncoder'

  5. "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:

  1. It allows validating passwords in modern and legacy formats.

  2. It allows for upgrading to a newer encoding in the future.

  3. It 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, whoch 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 programtically generate password in storage format, you can use the below code snippet:

Generate password using default password encoder (bcrypt for now)
User user = User.withDefaultPasswordEncoder()
  .username("user")
  .password("password")
  .roles("user")
  .build();
System.out.println(user.getPassword());
Sample output
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

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 may be preferred.

As per spring docs

This NoOpPasswordEncoder is provided for legacy and testing purposes only and is not considered secure.


Top articles in this category:
  1. Top 30 Hibernate and Spring Data JPA interview questions
  2. Top 50 Multi-threading Java Interview Questions for Investment Bank
  3. Cracking core java interviews - question bank
  4. UBS Top 10 Java Interview Questions
  5. Citibank Java developer interview questions
  6. Sapient Global Market Java Interview Questions and Coding Exercise
  7. ION Trading Java Interview Questions



Find more on this topic:
Java Interviews image
Java Interviews

Interview - Product Companies, eCommerce Companies, Investment Banking, Healthcare Industry, Service Companies and Startups.

Last updated 1 week ago


Recommended books for interview preparation:

This website uses cookies to ensure you get the best experience on our website. more info