1

I'm trying to use JDBC Authentication for a REST Controller using Postgresql database. The Configuration class, which manages the authentication is the following one:

@Configuration
@EnableAutoConfiguration
public class JDBCSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dataSource;
    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource)
                .usersByUsernameQuery("select username,password, enabled from users where username=?")
                .authoritiesByUsernameQuery("select username, role from user_roles where username=?");
    }



    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                //HTTP Basic authentication
                .httpBasic()
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/**").hasRole("USER")
                .antMatchers(HttpMethod.POST, "/").hasRole("ADMIN")
                .and()
                .csrf().disable()
                .formLogin().disable();
    }


}

The Datasource configuration should be ok:

spring.datasource.url=jdbc:postgresql://localhost:5432/springdb
spring.datasource.username=user
spring.datasource.password=password

However, when I try to invoke a method of the Controller, with the User in Role:

@RequestMapping(path= "/", method = RequestMethod.GET, produces = {"application/json"})
public List<Customer> find()
{
    return repository.getCustomers();
}

Then it results in the following error:

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
        at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:244) ~[spring-security-core-5.1.6.RELEASE.jar:5.1.6.RELEASE]
        at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:198) ~[spring-security-core-5.1.6.RELEASE.jar:5.1.6.RELEASE]
        at org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$LazyPasswordEncoder.matches(AuthenticationConfiguration.java:289) ~[spring-security-config-5.1.6.RELEASE.jar:5.1.6.RELEASE]

Is there anything wrong in the Configuration class? Thanks

Carla
  • 3,064
  • 8
  • 36
  • 65

1 Answers1

2

Issue

  • You created the users and password directly in the database. But Spring security expects a prefix in front of your password.
  • You are checking for the role like hasRole("USER") which is equivalent to hasAuthority("ROLE_USER")

Solution:

  1. To fix the password issue, you can use any of the following but not both:

    • If the password is testing1, you will have to store the password as {noop}testing1. So update your password in the database accordingly.

    • Define as NoOpPasswordEncoder bean as your password encoder.

  2. To fix the authorisation issue:

    • Prefix your authorities in the database with ROLE_
@Bean
public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}

Explanation

  • When you don't configure a password encoder, Spring security uses DelegatingPasswordEncoder as the password encoder. It is actually a list of the following encoders.
  public static PasswordEncoder createDelegatingPasswordEncoder() {
    String encodingId = "bcrypt";
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    encoders.put(encodingId, new BCryptPasswordEncoder());
    encoders.put("ldap", new LdapShaPasswordEncoder());
    encoders.put("MD4", new Md4PasswordEncoder());
    encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
    encoders.put("noop", NoOpPasswordEncoder.getInstance());
    encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
    encoders.put("scrypt", new SCryptPasswordEncoder());
    encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
    encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
    encoders.put("sha256", new StandardPasswordEncoder());
    encoders.put("argon2", new Argon2PasswordEncoder());

    return new DelegatingPasswordEncoder(encodingId, encoders);
}
  • Since it has a list of password encoders, after retrieving the user and password, it does not know which password encoder was used to encrypt the password.

  • To solve this problem, spring security expects a prefix to be added to the password. So if the password was created using BCryptPasswordEncoder, it stores it as {bcrypt}xxx-sdsa-bcrypt-pasa. So on retrieval, it can decide which password encoder was used.

  • Thanks for your detailed answer. Unfortunately, I've tried both solutions (changing the db password to '{noop}password' and adding the PasswordEncoder in the @Connfiguration class however now I'm getting a 403 Forbidden (instead of a 401 error). I guess this usually indicates that user/passwords are OK but the Role is not allowed to access that resource. – Carla Jul 03 '20 at 10:57
  • 1
    Are you roles stored with `ROLE_` prefix? If not can you add it to database as well? You need the above plus this change – Kavithakaran Kanapathippillai Jul 03 '20 at 11:00
  • No they weren't :-( it works now. Thanks for stopping me from banging the head in the wall – Carla Jul 03 '20 at 17:33