Я рассматривал вопрос переполнения стека о преобразовании хэшей паролей и понял, что это возможно и довольно удобно при использовании плагина spring-security-core для автоматизации процесса.
Для начала нам понадобится PasswordEncoder
который может работать с обоими алгоритмами. Здесь я предполагаю, что вы будете конвертировать из SHA-256 (необязательно с солью) в bcrypt, но общий подход в основном не зависит от алгоритмов. Sha256ToBCryptPasswordEncoder
всегда будет хэшировать новые пароли, используя bcrypt, но может обнаружить разницу между хешами от SHA-256 и bcrypt в isPasswordValid
:
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
package com.burtbeckwith.grails.security; import grails.plugin.springsecurity.authentication.encoding.BCryptPasswordEncoder; import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder; public class Sha256ToBCryptPasswordEncoder implements org.springframework.security.authentication.encoding.PasswordEncoder { protected MessageDigestPasswordEncoder sha256PasswordEncoder; protected BCryptPasswordEncoder bcryptPasswordEncoder; public String encodePassword(String rawPass, Object salt) { return bcryptPasswordEncoder.encodePassword(rawPass, null ); } public boolean isPasswordValid(String encPass, String rawPass, Object salt) { if (encPass.startsWith( "$2a$10$" ) && encPass.length() == 60 ) { // already bcrypt return bcryptPasswordEncoder.isPasswordValid( encPass, rawPass, null ); } if (encPass.length() == 64 ) { return sha256PasswordEncoder.isPasswordValid( encPass, rawPass, salt); } // TODO return false ; } /** * Dependency injection for the bcrypt password encoder * @param encoder the encoder */ public void setBcryptPasswordEncoder(BCryptPasswordEncoder encoder) { bcryptPasswordEncoder = encoder; } /** * Dependency injection for the SHA-256 password encoder * @param encoder the encoder */ public void setSha256PasswordEncoder( MessageDigestPasswordEncoder encoder) { sha256PasswordEncoder = encoder; } } |
Это требует внедрения зависимостей для правильно сконфигурированных кодеров SHA-256 и bcrypt, и мы это немного рассмотрим.
Sha256ToBCryptPasswordEncoder
не может вносить какие-либо изменения, так как доступна только информация о пароле, поэтому мы DaoAuthenticationProvider
подкласс DaoAuthenticationProvider
и DaoAuthenticationProvider
эту работу в DaoAuthenticationProvider
:
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
package com.burtbeckwith.grails.security import grails.plugin.springsecurity.SpringSecurityUtils import grails.plugin.springsecurity.userdetails.GrailsUser import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.authentication.dao.DaoAuthenticationProvider import org.springframework.security.core.AuthenticationException import org.springframework.security.core.userdetails.UserDetails class PasswordFixingDaoAuthenticationProvider extends DaoAuthenticationProvider { def grailsApplication protected void additionalAuthenticationChecks( UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { super .additionalAuthenticationChecks userDetails, authentication // if we got this far the password was ok String oldHashedPassword = userDetails.getPassword() if (oldHashedPassword.startsWith( '$2a$10$' ) && oldHashedPassword.length() == 60 ) { // already bcrypt return } if (oldHashedPassword.length() != 64 ) { // TODO return } String bcryptPassword = getPasswordEncoder().encodePassword( authentication.credentials, null ) // use HQL to update the password in the database directly def conf = SpringSecurityUtils.securityConfig String userClassName = conf.userLookup.userDomainClassName Class<?> User = grailsApplication.getDomainClass(userClassName).clazz def args = [p: bcryptPassword] String hql = 'update ' + User.name + ' u set u.password=:p where ' if (userDetails instanceof GrailsUser) { hql += 'u.id=:id' args.id = userDetails.id } else { hql += 'u.' + conf.userLookup.usernamePropertyName + '=:un' args.un = userDetails.username } User.withNewSession { User.withTransaction { User.executeUpdate hql, args } } } } |
Вызов super.additionalAuthenticationChecks()
гарантирует, что был предоставлен пароль, и он будет проверен с помощью SHA-256 или bcrypt с помощью Sha256ToBCryptPasswordEncoder
, поэтому, если не Sha256ToBCryptPasswordEncoder
исключение, безопасно обновить пароль. Обратите внимание, что код обновления является общим и может быть сделан более компактным путем жесткого кодирования имен ваших классов и свойств.
Мы регистрируем Sha256ToBCryptPasswordEncoder
в качестве bean-компонента passwordEncoder
и создаем bean-компоненты bcryptPasswordEncoder
и sha256PasswordEncoder
, настроенные с использованием используемых параметров SHA-256, и параметры bcrypt, которые будут использоваться (настройте их в Config.groovy
как описано в документации). Также настройте переопределение bean-компонента daoAuthenticationProvider
в качестве PasswordFixingDaoAuthenticationProvider
с той же конфигурацией, что и в SpringSecurityCoreGrailsPlugin.groovy
с добавлением ссылки grailsApplication
:
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
32
33
34
35
36
|
import grails.plugin.springsecurity.SpringSecurityUtils import grails.plugin.springsecurity.authentication.encoding.BCryptPasswordEncoder import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder import com.burtbeckwith.grails.security.PasswordFixingDaoAuthenticationProvider import com.burtbeckwith.grails.security.Sha256ToBCryptPasswordEncoder beans = { def conf = SpringSecurityUtils.securityConfig bcryptPasswordEncoder(BCryptPasswordEncoder, conf.password.bcrypt.logrounds) // 10 sha256PasswordEncoder(MessageDigestPasswordEncoder, conf.password.algorithm) { encodeHashAsBase64 = conf.password.encodeHashAsBase64 // false iterations = conf.password.hash.iterations // 10000 } passwordEncoder(Sha256ToBCryptPasswordEncoder) { bcryptPasswordEncoder = ref( 'bcryptPasswordEncoder' ) sha256PasswordEncoder = ref( 'sha256PasswordEncoder' ) } daoAuthenticationProvider(PasswordFixingDaoAuthenticationProvider) { userDetailsService = ref( 'userDetailsService' ) passwordEncoder = ref( 'passwordEncoder' ) userCache = ref( 'userCache' ) saltSource = ref( 'saltSource' ) preAuthenticationChecks = ref( 'preAuthenticationChecks' ) postAuthenticationChecks = ref( 'postAuthenticationChecks' ) authoritiesMapper = ref( 'authoritiesMapper' ) hideUserNotFoundExceptions = conf.dao.hideUserNotFoundExceptions // true grailsApplication = ref( 'grailsApplication' ) } } |
При такой конфигурации пароли новых пользователей будут хэшироваться с помощью bcrypt, а действительные пароли существующих пользователей будут конвертироваться в bcrypt с использованием паролей в виде открытого текста, используемых при входе в систему. Как только ваши пользователи будут конвертированы, отмените эти изменения и перейдите на стандартный подход bcrypt. Это может включать удаление атрибута grails.plugin.springsecurity.password.algorithm
и всей конфигурации соли, так как bcrypt не поддерживает соль, удаление Sha256ToBCryptPasswordEncoder
и PasswordFixingDaoAuthenticationProvider
, а также удаление bcryptPasswordEncoder
и sha256PasswordEncoder
bcryptPasswordEncoder
sha256PasswordEncoder
bcryptPasswordEncoder
sha256PasswordEncoder
bcryptPasswordEncoder
sha256PasswordEncoder
bcryptPasswordEncoder
sha256PasswordEncoder
bcryptPasswordEncoder
sha256PasswordEncoder
bcryptPasswordEncoder
sha256PasswordEncoder
bcryptPasswordEncoder
sha256PasswordEncoder
bcryptPasswordEncoder
sha256PasswordEncoder
daoAuthenticationProvider
поскольку Config.groovy
настроенные плагином с использованием настроек Config.groovy
, будут достаточными. Также, если вы добавили соль в метод encodePassword
класса encodePassword
, например,
1
2
3
|
protected void encodePassword() { password = springSecurityService.encodePassword(password, username) } |
преобразовать его обратно в значение по умолчанию без соли:
1
2
3
|
protected void encodePassword() { password = springSecurityService.encodePassword(password) } |