«1. Введение

В последней версии Spring Security многое изменилось. Одним из таких изменений является то, как мы можем обрабатывать кодировку паролей в наших приложениях.

В этом уроке мы рассмотрим некоторые из этих изменений.

Позже мы увидим, как настроить новый механизм делегирования и как обновить нашу существующую кодировку пароля так, чтобы наши пользователи не узнали ее.

2. Соответствующие изменения в Spring Security 5.x

Команда Spring Security объявила PasswordEncoder в org.springframework.security.authentication.encoding устаревшим. Это был логичный ход, так как старый интерфейс не был рассчитан на случайно сгенерированную соль. Следовательно, версия 5 удалила этот интерфейс.

Кроме того, Spring Security меняет способ обработки закодированных паролей. В предыдущих версиях каждое приложение использовало только один алгоритм шифрования пароля.

По умолчанию этим занимается StandardPasswordEncoder. Он использовал SHA-256 для кодирования. Сменив кодировщик пароля, мы могли бы переключиться на другой алгоритм. Но наше приложение должно было придерживаться ровно одного алгоритма.

Версия 5.0 вводит концепцию делегирования кодировки паролей. Теперь мы можем использовать разные кодировки для разных паролей. Spring распознает алгоритм по идентификатору, предшествующему закодированному паролю.

Вот пример пароля, закодированного в bcrypt:

{bcrypt}$2b$12$FaLabMRystU4MLAasNOKb.HUElBAabuQdX59RWHq5X.9Ghm692NEi

Обратите внимание, что в самом начале bcrypt указывается в фигурных скобках.

3. Конфигурация делегирования

Если хэш пароля не имеет префикса, процесс делегирования использует кодировщик по умолчанию. Следовательно, по умолчанию мы получаем StandardPasswordEncoder.

Это делает его совместимым с конфигурацией по умолчанию предыдущих версий Spring Security.

В версии 5 Spring Security представляет PasswordEncoderFactories.createDelegatingPasswordEncoder(). Этот фабричный метод возвращает сконфигурированный экземпляр DelegationPasswordEncoder.

Для паролей без префикса этот экземпляр обеспечивает только что упомянутое поведение по умолчанию. А для хэшей паролей, содержащих префикс, делегирование выполняется соответствующим образом.

Команда Spring Security перечисляет поддерживаемые алгоритмы в последней версии соответствующего JavaDoc.

Конечно, Spring позволяет настроить это поведение.

Давайте предположим, что мы хотим поддерживать:

    bcrypt в качестве нашего нового сценария по умолчанию в качестве альтернативы SHA-256 в качестве используемого в настоящее время алгоритма.

Конфигурация для этой установки будет выглядеть следующим образом:

@Bean
public PasswordEncoder delegatingPasswordEncoder() {
    PasswordEncoder defaultEncoder = new StandardPasswordEncoder();
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    encoders.put("bcrypt", new BCryptPasswordEncoder());
    encoders.put("scrypt", new SCryptPasswordEncoder());

    DelegatingPasswordEncoder passworEncoder = new DelegatingPasswordEncoder(
      "bcrypt", encoders);
    passworEncoder.setDefaultPasswordEncoderForMatches(defaultEncoder);

    return passworEncoder;
}

4. Миграция алгоритма кодирования паролей

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

Давайте представим, что мы хотим изменить кодировку с SHA-256 на bcrypt, однако мы не хотим, чтобы наш пользователь менял свои пароли.

Одним из возможных решений является использование запроса на вход. На этом этапе мы можем получить доступ к учетным данным в виде обычного текста. В этот момент мы можем взять текущий пароль и перекодировать его.

Следовательно, для этого мы можем использовать Spring AuthenticationSuccessEvent. Это событие срабатывает после того, как пользователь успешно вошел в наше приложение.

Вот пример кода:

@Bean
public ApplicationListener<AuthenticationSuccessEvent>
  authenticationSuccessListener( PasswordEncoder encoder) {
    return (AuthenticationSuccessEvent event) -> {
        Authentication auth = event.getAuthentication();

        if (auth instanceof UsernamePasswordAuthenticationToken
          && auth.getCredentials() != null) {

            CharSequence clearTextPass = (CharSequence) auth.getCredentials();
            String newPasswordHash = encoder.encode(clearTextPass);

            // [...] Update user's password

            ((UsernamePasswordAuthenticationToken) auth).eraseCredentials();
        }
    };
}

В предыдущем фрагменте:

    Мы получили пароль пользователя в виде открытого текста из предоставленных данных аутентификации Создали новый хэш пароля с новым алгоритмом Удалили пароль в виде открытого текста из токена аутентификации

По умолчанию извлечение пароля в виде открытого текста невозможно, поскольку Spring Security удаляет его как можно скорее.

Следовательно, нам нужно настроить Spring так, чтобы он сохранял версию пароля в открытом виде.

Кроме того, нам необходимо зарегистрировать наше делегирование кодирования:

@Configuration
public class PasswordStorageWebSecurityConfigurer
  extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) 
      throws Exception {
        auth.eraseCredentials(false)
          .passwordEncoder(delegatingPasswordEncoder());
    }

    // ...
}

5. Заключение

В этой быстрой статье мы рассказали о некоторых новых функциях кодирования паролей, доступных в версии 5.x.

«Мы также увидели, как настроить несколько алгоритмов кодирования паролей для кодирования наших паролей. Кроме того, мы изучили способ изменить кодировку пароля, не нарушая существующую.

Наконец, мы описали, как использовать события Spring для прозрачного обновления зашифрованного пароля пользователя, что позволяет нам легко изменять нашу стратегию кодирования, не раскрывая это нашим пользователям.

Наконец, как всегда, все примеры кода доступны в нашем репозитории GitHub.