Недавно я работал в проекте, который использовал пользовательский 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, являются их собственными. |