Search code examples
javaspringspring-security

Is there a way to configure password encoder for default password?


I use overridden values for spring security username and password. Following properties are in my application.properties.

spring.security.user.name=myUser
spring.security.user.password=myPassword
spring.security.user.roles=admin

I would like to encrypt the password value as follows:

spring.security.user.name=myUser
spring.security.user.password={bcrypt}hashedpassword valuevalue
spring.security.user.roles=admin

I added PasswordEncoder in my SpringConfig:

@Bean
public PasswordEncoder encoder() {
    return new BCryptPasswordEncoder();
}

In some example I noticed that there is for AuthenitcationManagerBuilder but I do not know what datasource should be used. What else do I need to use encrypted password for default user?

@Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {

Adding my Spring Security config as a reference:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/api/v1/custom").hasRole("admin")
                .anyRequest().authenticated()
                .and()
            .httpBasic();

        http
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

Solution

  • Grabbing:

    $2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
    

    for input:

    hello
    

    ... from (e.g. thx): https://bcrypt-generator.com/

    With:

    • spring-starter (web, security),
    • application.properties:
      spring.security.user.password={bcrypt}$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
      
    • and any (rest)controller:
      package com.example.demo;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.annotation.Bean;
      import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      import org.springframework.security.crypto.password.PasswordEncoder;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      @SpringBootApplication
      @RestController
      public class DemoApplication {
      
         public static void main(String[] args) {
          SpringApplication.run(DemoApplication.class, args);
         }
      
         //  @Bean
         //  public PasswordEncoder encoder() {
         //   return new BCryptPasswordEncoder();
         //  }
      
         @GetMapping("secured")
         public String secured() {
          return "Hello Admin";
         }
      }
      
    • and without additional config,
    • we can login with user=user and password=hello.

    Adding:

    @Bean
    public PasswordEncoder encoder() {
      return new BCryptPasswordEncoder();
    }
    

    breaks this behavior!! To "fix it", we can (application.properties):

    spring.security.user.password=$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
    

    .. omit the "tag" {bcrypt}! ...

    Responsible for "tags"/capable of several algorithms is the DelegatingPasswordEncoder, which is auto configured (via [AuthenticationConfiguration|HttpSecurityConfiguration].LazyPasswordEncoder ..if no other PasswordEncoder bean found) and uses PasswordEncoderFactories behind the scenes, like (currently) this:

    @SuppressWarnings("deprecation")
    public static PasswordEncoder createDelegatingPasswordEncoder() {
      String encodingId = "bcrypt";
      Map<String, PasswordEncoder> encoders = new HashMap<>();
      encoders.put(encodingId, new BCryptPasswordEncoder());
      encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
      encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
      encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
      encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
      encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
      encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
      encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
      encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
      encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
      encoders.put("SHA-256",
              new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
      encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
      encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
      encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
      return new DelegatingPasswordEncoder(encodingId, encoders);
    }
    

    EDIT:

    No, also introducing:

    @Configuration
    class WebSecurityConfig {
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
          http
              .csrf()
              .disable()
              .authorizeHttpRequests().requestMatchers("/secured").hasRole("admin")
              .anyRequest()
              .authenticated()
              .and()
              .httpBasic();
    
          http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
          return http.build();
        }
      }
    }
    

    Plus:

    spring.security.user.roles=admin
    

    Doesn't change my observations:

    • When "custom password encoder"
    • and $2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
      • can login with user:hello (and view /secured response)
    • with default/-out password encoder
    • and {bcrypt}$2a$12$7Bg57uTtN7GWIdiqRW4h5e/aOUFagHwkEGv9byUr0bb/QbUU8S4rS
      • can login with user:hello (and view /secured response)

    (it only switches from form to basic login:)

    Test (good/app config):

    package com.example.demo;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
    import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
    import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
    import org.springframework.test.web.servlet.MockMvc;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    
    @WebMvcTest
    class WithAppConfigTest {
    
      @Autowired
      MockMvc mockMvc;
    
      @Test
      void expectAccess() throws Exception {
        mockMvc.perform(
            get("/secured")
                .with(httpBasic("user", "hello"))
        ).andExpectAll(
            authenticated(),
            status().isOk(),
            content().string("Hello Admin"));
      }
    
      @Test
      void expectUnauthorized() throws Exception {
        mockMvc.perform(
            get("/secured")
                .with(httpBasic("user", "wrong"))
        ).andExpectAll(
            unauthenticated(),
            status().isUnauthorized(),
            content().bytes(new byte[0]));
      }
    }
    

    Test forbidden:

    @WebMvcTest(properties = "spring.security.user.roles=foo,bar,baz")
    class WithFakeConfigTest {
    
      @Autowired
      MockMvc mockMvc;
    
      @Test
      void expectForbidden() throws Exception {
        mockMvc.perform(
            get("/secured")
                .with(httpBasic("user", "hello"))
        ).andExpectAll(
            authenticated(), // !, but:
            status().isForbidden(),
            content().bytes(new byte[0]));
      }
    }