Статьи

Автоматическое преобразование хэшей паролей в Grails Spring-Security-Core

Я рассматривал вопрос переполнения стека о преобразовании хэшей паролей и понял, что это возможно и довольно удобно при использовании плагина 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)
}