Недавно я работал в проекте, который использовал пользовательский PasswordEncoder и было требование перенести его в bcrypt .  Текущие пароли хранятся в виде hash что означает, что невозможно вернуть его к исходной String — по крайней мере, не простым способом. 
  Задача состояла в том, чтобы поддержать обе реализации, старое хеш-решение и новую реализацию bcrypt .  После небольшого исследования я смог найти DelegatingPasswordEncoder Spring Security 5 . 
Познакомьтесь с DelegatingPasswordEncoder
  Класс DelegatingPasswordEncoder позволяет поддерживать несколько password encoders на основе префикса .  Пароль хранится так: 
| 
 1 
2 
 | 
{bcrypt}$2a$10$vCXMWCn7fDZWOcLnIEhmK.74dvK1Eh8ae2WrWlhr2ETPLoxQctN4.{noop}plaintextpassword | 
  Spring Security 5 предоставляет удобный класс PasswordEncoderFactories , в настоящее время этот класс поддерживает следующие кодировщики: 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
 | 
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());    return new DelegatingPasswordEncoder(encodingId, encoders);} | 
  Теперь вместо объявления единого PasswordEncoder мы можем использовать PasswordEncoderFactories , например, такой фрагмент кода: 
| 
 1 
2 
3 
4 
 | 
@Beanpublic PasswordEncoder passwordEncoder() {    return PasswordEncoderFactories.createDelegatingPasswordEncoder();} | 
Добавление пользовательского кодировщика
  Теперь, возвращаясь к моей первоначальной проблеме, по старым причинам есть собственное решение для password encoding , и удобный PasswordEncoderFactories ничего не знает об этом, чтобы решить, что я создал класс, похожий на PasswordEncoderFactories и добавил все встроенные -в кодировщиках вместе с моим, вот пример реализации: 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
 | 
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.DelegatingPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;import java.util.HashMap;import java.util.Map;class DefaultPasswordEncoderFactories {    @SuppressWarnings("deprecation")    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("custom", new CustomPasswordEncoder());        return new DelegatingPasswordEncoder(encodingId, encoders);    }} | 
  А затем я объявил свой @Bean используя вместо него DefaultPasswordEncoderFactories . 
  После первого запуска я понял другую проблему, мне нужно было запустить скрипт SQL чтобы обновить все существующие пароли, добавив префикс {custom} чтобы инфраструктура могла правильно связать префикс с правильным PasswordEncoder , не поймите меня неправильно, это прекрасное решение, но я действительно не хотел возиться с существующими паролями в базе данных, и, к счастью для нас, класс DelegatingPasswordEncoder позволяет нам устанавливать PasswordEncoder умолчанию , это означает, что всякий раз, когда платформа пытается найти префикс в сохраненном пароле, откат к default чтобы попытаться декодировать его. 
Затем я изменил свою реализацию на следующее:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
 | 
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.DelegatingPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;import java.util.HashMap;import java.util.Map;class DefaultPasswordEncoderFactories {    @SuppressWarnings("deprecation")    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"));        DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(encodingId, encoders);        delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(new CustomPasswordEncoder());        return delegatingPasswordEncoder;    }} | 
  И объявление @Bean теперь: 
| 
 1 
2 
3 
4 
 | 
@Beanpublic PasswordEncoder passwordEncoder() {    return DefaultPasswordEncoderFactories.createDelegatingPasswordEncoder();} | 
Вывод
  Кодировщики паролей миграции — это реальная проблема, и Spring Security 5 предоставляет довольно удобный способ легко справиться с ней, поддерживая несколько PasswordEncoder одновременно. 
сноска
- Код, используемый для этого урока, можно найти на GitHub .
 - DelegatingPasswordEncoder — Spring Docs
 
| 
 Опубликовано на Java Code Geeks с разрешения Маркоса Барберо, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Перенос кодировщика паролей с помощью Spring Security 5 Мнения, высказанные участниками Java Code Geeks, являются их собственными.  |