Как переопределить BCryptPasswordEncoder по умолчанию, созданный через PasswordEncoderFactories?

#spring-security

#spring-безопасность

Вопрос:

Я знаю, что в Spring Security возникнет следующее:

 There was an unexpected error (type=Internal Server Error, status=500).
There is no PasswordEncoder mapped for the id "null"
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
 

И решение заключается в определении a PasswordEncoder . Для простоты можно определить следующее:

 @Bean
PasswordEncoder encoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
 

Теперь, за кулисами createDelegatingPasswordEncoder() , метод определяется как (до сих пор до сих пор для Spring Security 5.4.2) (подробнее см. Класс PasswordEncoderFactories):

 @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", new Pbkdf2PasswordEncoder());
    encoders.put("scrypt", new SCryptPasswordEncoder());
    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", new Argon2PasswordEncoder());
    return new DelegatingPasswordEncoder(encodingId, encoders);
}
 

Теперь о классе BCryptPasswordEncoder, он работает с некоторыми значениями по умолчанию, такими как:

  • версия: $2a
  • сила: 10

Что произойдет, если объявить следующее:

 @Bean
PasswordEncoder bcryptEncoder() {
    return new BCryptPasswordEncoder(BCryptVersion.$2Y, 12);
}
 

Как я могу добавить или переопределить значение по умолчанию BCryptPasswordEncoder , созданное с помощью пользовательского BCryptPasswordEncoder , в настройки по умолчанию? Я хочу сохранить все остальные значения по умолчанию

Примечание: в классе PasswordEncoderFactories (для createDelegatingPasswordEncoder метода) класс DelegatingPasswordEncoder используется за кулисами. Этот класс также не предлагает подход к переопределению.

Ответ №1:

Если вы создаете новое приложение, вам, скорее всего, не понадобится DelegatingPasswordEncoder и вместо этого следует использовать, например, адаптивный кодировщик паролей с односторонней функцией BCryptPasswordEncoder .

Для этого вы можете представить a BCryptPasswordEncoder как компонент.

 @Bean
PasswordEncoder bcryptEncoder() {
    return new BCryptPasswordEncoder(BCryptVersion.$2Y, 12);
}
 

Затем, когда пользователь регистрируется, вы можете закодировать его пароль с помощью BCryptPasswordEncoder , прежде чем сохранить его в хранилище данных. Например:

 UserDetails userDetails = User
        .withUsername(username)
        .password(bcryptEncoder.encode(password))
        .roles("USER")
        .build();
 

Если вы переносите существующее приложение, это DelegatingPasswordEncoder полезно.

DelegatingPasswordEncoder Позволяет проверять пароли в нескольких форматах. Он использует идентификатор с префиксом (например {bcrypt} , ) для поиска, который PasswordEncoder следует использовать.

Рассмотрим устаревшее приложение, которое использует пароли открытого текста.
Чтобы перенести приложение, вы должны добавить к открытым текстовым паролям префикс {noop} и использовать DelegatingPasswordEncoder , как показано ниже:

 @Bean
public PasswordEncoder delegatingPasswordEncoder() {
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    encoders.put("bcrypt", new BCryptPasswordEncoder(BCryptVersion.$2Y, 12));
    encoders.put("noop", NoOpPasswordEncoder.getInstance());
    return new DelegatingPasswordEncoder("bcrypt", encoders);
}
 

Это DelegatingPasswordEncoder позволит кодировать и проверять любой вновь созданный пароль с использованием BCryptPasswordEncoder , сохраняя при этом возможность проверки устаревших паролей открытого текста с использованием NoOpPasswordEncoder .

Spring Security предоставляет этот PasswordEncoderFactories.createDelegatingPasswordEncoder() метод в качестве удобного по умолчанию, но маловероятно, что ваше приложение использует так много разных кодировок паролей.
Более вероятно, что ваше приложение использует 2 разные кодировки: устаревшую (например, noop) и современную (например, bcrypt), и в этом случае вы можете использовать PasswordEncoder аналогичную delegatingPasswordEncoder описанной выше.

Смысл этих примеров в том, чтобы сказать, что в большинстве ситуаций вам не нужны значения по умолчанию, которые установлены createDelegatingPasswordEncoder . Однако, если вы все еще хотите использовать кодеры из createDelegatingPasswordEncoder , за исключением bcrypt , вы можете использовать PasswordEncoder , который выглядит следующим образом:

 @Bean
public PasswordEncoder delegatingPasswordEncoder() {
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    // Use this encoder for bcrypt
    encoders.put("bcrypt", new BCryptPasswordEncoder(BCryptVersion.$2Y, 12));
    DelegatingPasswordEncoder delegatingPasswordEncoder =
            new DelegatingPasswordEncoder("bcrypt", encoders);

    PasswordEncoder defaultDelegatingPasswordEncoder =
            PasswordEncoderFactories.createDelegatingPasswordEncoder();
    // If a password ID does not match "bcrypt", use defaultDelegatingPasswordEncoder
    delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(defaultDelegatingPasswordEncoder);

    return delegatingPasswordEncoder;
}