Недавно я работал в проекте, который использовал пользовательский 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
|
@Bean public 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
|
@Bean public PasswordEncoder passwordEncoder() { return DefaultPasswordEncoderFactories.createDelegatingPasswordEncoder(); } |
Вывод
Кодировщики паролей миграции — это реальная проблема, и Spring Security 5 предоставляет довольно удобный способ легко справиться с ней, поддерживая несколько PasswordEncoder
одновременно.
сноска
- Код, используемый для этого урока, можно найти на GitHub .
- DelegatingPasswordEncoder — Spring Docs
Опубликовано на Java Code Geeks с разрешения Маркоса Барберо, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Перенос кодировщика паролей с помощью Spring Security 5 Мнения, высказанные участниками Java Code Geeks, являются их собственными. |