Почему я могу аутентифицироваться с помощью хэша пароля в Java Spring Security

Я использую spring-boot-starter-security, Я настроил свой WebSecurityConfigation использовать DaoAuthenticationProvider поставщик и BCryptPasswordEncoder для аутентификации. Так же UserDetailsService реализация возвращает User объект с password поле установлено на фактический хеш.

Вроде нормально работает. Однако я заметил, что могу успешно пройти аутентификацию либо с помощью пароля, либо с помощью хэша.

Например, сам пароль является сгенерированным UUID 51a80a6a-8618-4583-98d2-d77d103a62c6 который был закодирован в $2a$10$u4OSZf7B9yJvQ5UYNNpy7O4f3g0gfUMl2Xmm3h282W.3emSN3WqxO,

Полная настройка веб-безопасности:

@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;
}

Почему я могу аутентифицироваться с обеими строками? Я делаю что-то не так, или это ожидается или какая-то конфигурация? Я не смог ничего найти в документах.

2 ответа

Решение

Я думаю, это происходит потому, что в соответствии с вашей конфигурацией вы получите два DaoAuthenticationProviders. Один явный, настроенный вами, а другой неявный, который настраивается под капотом, когда вы звоните auth.userDetailsService(userDetailsService);и для этого неявного провайдера вы не устанавливаете кодировщик пароля.

Попробуйте это:

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

И удалите своего провайдера, настроенного вручную - кажется, что он вам на самом деле не нужен.

Надеюсь, поможет.

Наилучшим подходом было бы подключить отладчик. Фактическая логика использования PasswordEncoder для сопоставления пароля находится в additionalAuthenticationChecks метод 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);
        }
    }

Это делегаты 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);
}
Другие вопросы по тегам