0

I am using spring-boot-starter-security. I configured my WebSecurityConfigation to use DaoAuthenticationProvider provider and BCryptPasswordEncoder for authentication. Also the UserDetailsService implementation returns a User object with the password field set to the actual hash.

It seems to work fine. However i noticed that i could successfully authenticate with either the password or the hash.

For example the password itself is a generated UUID 51a80a6a-8618-4583-98d2-d77d103a62c6 which was encoded to $2a$10$u4OSZf7B9yJvQ5UYNNpy7O4f3g0gfUMl2Xmm3h282W.3emSN3WqxO.

Full web security configuration:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DemoUserDetailsService userDetailsService;

    @Autowired
    private DaoAuthenticationProvider authenticationProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
        auth.userDetailsService(userDetailsService);
        auth.inMemoryAuthentication().withUser("user").password("password").roles("SUPER", "BASIC");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").hasRole("BASIC").and().httpBasic();
        http.csrf().disable();
    }
}

@Service
public class DemoUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        UserDAO userDAO = userRepository.findByEmailAndActivated(email);
        if (userDAO == null) {
            throw new UsernameNotFoundException(String.format("Email %s not found", email));
        }
        return new User(email, userDAO.getPasswordHash(), getGrantedAuthorities(email));
    }

    private Collection<? extends GrantedAuthority> getGrantedAuthorities(String email) {
        return asList(() -> "ROLE_BASIC");
    }
}

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

@Bean
public DaoAuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(userDetailsService);
    authenticationProvider.setPasswordEncoder(passwordEncoder);
    return authenticationProvider;
}

Why am i able to authenticate with both strings? Am i doing something wrong, or is this expected or some configuration? I was unable to find anything in docs.

Tom
  • 2,481
  • 1
  • 15
  • 16
  • What are the odds but ... check by encoding the encoded String, this could be the same output (I really doubt that). Since a result hash is not unique, this is possible, unlikely but possible – AxelH Oct 09 '17 at 06:28
  • show the configuration, cause i have also implemented security on bcrypt and i do not notice any kind of this. – underwater ranged weapon Oct 09 '17 at 06:29
  • @shutdown -h now what configuration do you exactly mean? – Tom Oct 09 '17 at 07:03
  • I'd say dig deep with the debugger. Verify that the values are what you expect them to be. – Kayaman Oct 09 '17 at 07:21
  • Debugging shows me that if i use the password, it is correctly hashed and checked. This is fine. However - when i use the hash, i see that the PlaintextPasswordEncoder is used to check the password. This is why i'm able to use the hash. I still don't see how the PlaintextPasswordEncoder ends up in my code though. – Tom Oct 09 '17 at 07:54

2 Answers2

2

I guess this thing happens because according to your config you're actually get two DaoAuthenticationProviders. One is explicit, configured by you, and one is implicit, that gets configured under the hood, when you're calling auth.userDetailsService(userDetailsService);, and for this implicit provider you're not setting password encoder.

Try this out:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); 
    auth.inMemoryAuthentication().withUser("user").password("password").roles("SUPER", "BASIC");
}

And remove your manually configured provider - seems that you're actually dont need it.

Hope it helps.

Leffchik
  • 1,950
  • 14
  • 16
  • Actually you're right. Adding (userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); helped. – Tom Oct 09 '17 at 08:44
-2

Best approach would be to hook up the debugger. The actual logic of using PasswordEncoder for matching password is in additionalAuthenticationChecks method of DaoAuthenticationProvider

 protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        Object salt = null;

        if (this.saltSource != null) {
            salt = this.saltSource.getSalt(userDetails);
        }

        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
        }

        String presentedPassword = authentication.getCredentials().toString();

        if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
        }
    }

This delegates to BCryptPasswordEncoder

public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (encodedPassword == null || encodedPassword.length() == 0) {
            logger.warn("Empty encoded password");
            return false;
        }
    if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
        logger.warn("Encoded password does not look like BCrypt");
        return false;
    }

    return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
}
Shailendra
  • 8,874
  • 2
  • 28
  • 37