Я рассматривал вопрос переполнения стека о преобразовании хэшей паролей и понял, что это возможно и довольно удобно при использовании плагина 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.securityimport grails.plugin.springsecurity.SpringSecurityUtilsimport grails.plugin.springsecurity.userdetails.GrailsUserimport org.springframework.security.authentication.UsernamePasswordAuthenticationTokenimport org.springframework.security.authentication.dao.DaoAuthenticationProviderimport org.springframework.security.core.AuthenticationExceptionimport org.springframework.security.core.userdetails.UserDetailsclass PasswordFixingDaoAuthenticationProviderextends 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.SpringSecurityUtilsimport grails.plugin.springsecurity.authentication.encoding.BCryptPasswordEncoderimport org.springframework.security.authentication.encoding.MessageDigestPasswordEncoderimport com.burtbeckwith.grails.security.PasswordFixingDaoAuthenticationProviderimport com.burtbeckwith.grails.security.Sha256ToBCryptPasswordEncoderbeans = { 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)} |