0

I'm using the following web security configuration for my Spring boot app:

@EnableWebSecurity
@Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired 
    private AccountRepository accountRepository;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/login").permitAll()
            .and()
            .authorizeRequests()
                .antMatchers("/signup").permitAll()
            .and()
            .authorizeRequests()
                .anyRequest().authenticated()
            .and()
                .logout().logoutUrl("/logout").logoutSuccessUrl("/login").deleteCookies("auth_code").invalidateHttpSession(true)
            .and()
            // We filter the api/signup requests
            .addFilterBefore(
                new JWTSignupFilter("/signup", authenticationManager(), accountRepository),
                UsernamePasswordAuthenticationFilter.class)
            // We filter the api/login requests
            .addFilterBefore(
                new JWTLoginFilter("/login", authenticationManager()),
                UsernamePasswordAuthenticationFilter.class)
            // And filter other requests to check the presence of JWT in
            // header
            .addFilterBefore(new JWTAuthenticationFilter(userDetailsServiceBean()),
                UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {
        auth.userDetailsService(userDetailsServiceBean()).passwordEncoder(passwordEncoder());
    }

    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return new CustomUserDetailsService(accountRepository);
    }

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

As you can see, I use BCryptPasswordEncoder.

I have two problems:

1.Where is the salt for the BCryptPasswordEncoder stored ? According to my findings, it's random each time and it's stored somewhere in the database, but I didn't define any column for it. I'm confused over how it works.

2.I have a Signup filter:

public class JWTSignupFilter extends AbstractAuthenticationProcessingFilter {

    private AccountRepository accountRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public JWTSignupFilter(String url, AuthenticationManager authManager,
            AccountRepository accountRepository) {
        super(new AntPathRequestMatcher(url, "POST"));
        setAuthenticationManager(authManager);
        this.accountRepository = accountRepository;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
            HttpServletResponse res) throws AuthenticationException,
            IOException, ServletException {

        CustomUserDetails creds = new ObjectMapper().readValue(
                req.getInputStream(), CustomUserDetails.class);

        if (accountRepository.findByUsername(creds.getUsername()) != null) {
            throw new AuthenticationException("Duplicate username") {
                private static final long serialVersionUID = 1L;
            };
        }

        CustomUserDetails userDetails = new CustomUserDetails(
                creds.getUsername(), creds.getPassword(), true, true, true,
                true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("USER_ROLE"));

        accountRepository.save(userDetails);

        return getAuthenticationManager().authenticate(
                new UsernamePasswordAuthenticationToken(creds.getUsername(),
                        creds.getPassword()));
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req,
            HttpServletResponse res, FilterChain chain, Authentication auth) {
        TokenAuthenticationService.addAuthentication(res, auth.getName());
    }
}

As you can see I create a new UserDetails and save it in the account repository.

If I encode the password while creating the UserDetails object, it would create not the same password as when logging in. (the econded passowrds are different) I think this should be because of the different salts being used.

Any idea how I can fix this ?

Arian
  • 7,397
  • 21
  • 89
  • 177

1 Answers1

2

Bcrypt includes batteries: the text that is typically saved in the database contains both the salt and the number of iterations. There are online generators that can show it. For example, given the secret P@ssw0rd here is how it is serialized (4 iterations):

$2a$04$tpwXnhoO89cja8UZw3.hpulcAGzL1ps5cqzTLubh60csfEwna4N3W
$2a$04$VuLYo3y8e1ZITJPgW8LliOzdRa220D0frl5oSPQeFxAOlmvmsTCsK
$2a$04$bkChnYI84W3P3DW8YZrQc.yarPrW9kCDRAEp8ZKlap2BiO2Y.ThNa
...

Quoting this answer

  • 2a identifies the bcrypt algorithm version
  • 04 is the cost factor; 24 iterations of the key derivation function are used
  • vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa is the salt and the cipher text, concatenated and encoded in a modified Base-64. The first 22 characters decode to a 16-byte value for the salt. The remaining characters are cipher text to be compared for authentication.
Community
  • 1
  • 1
Raffaele
  • 20,627
  • 6
  • 47
  • 86
  • When a user signs up, the passed in password gets hashed and stored in a db. The next time user logs in, a password is passed in and hashed and compare with the already stored password. But, the salts are going to be different and the comparison fails. How does this work ? – Arian Apr 29 '17 at 16:28
  • 1
    The point is that the stored hash is never compared against as-is. You use something like [`BCrypt.checkpw`](http://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/crypto/bcrypt/BCrypt.html#checkpw-java.lang.String-java.lang.String-) – Raffaele Apr 29 '17 at 16:32