我一直在研究有关转换密码哈希的Stack Overflow问题 ,并意识到使用spring-security-core插件自动执行此过程是可能且相当方便的。
首先,我们需要一个可以同时使用两种算法的PasswordEncoder
。 在这里,我假设您将从SHA-256(可选用盐)转换为bcrypt,但一般方法大多与算法无关。 Sha256ToBCryptPasswordEncoder
将始终使用bcrypt哈希新密码,但可以在isPasswordValid
检测SHA-256和bcrypt的哈希值之间的区别:
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
,做了这项工作additionalAuthenticationChecks
:
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()
将确保提供了密码,并将通过Sha256ToBCryptPasswordEncoder
使用SHA-256或bcrypt对其进行Sha256ToBCryptPasswordEncoder
,因此,如果没有异常抛出,则可以安全地更新密码。 请注意,更新代码是通用的,可以通过对您的类和属性名称进行硬编码来使其更紧凑。
我们将Sha256ToBCryptPasswordEncoder
注册为passwordEncoder
Bean,并创建bcryptPasswordEncoder
和sha256PasswordEncoder
Bean, sha256PasswordEncoder
bean配置有正在使用的SHA-256设置以及将要使用的bcrypt设置(如docs中所述在Config.groovy
配置)。 另外,将daoAuthenticationProvider
的Bean覆盖配置为PasswordFixingDaoAuthenticationProvider
,其配置与SpringSecurityCoreGrailsPlugin.groovy
所做的配置相同,并添加grailsApplication
参考:
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
,并从resources.groovy
删除bcryptPasswordEncoder
和sha256PasswordEncoder
bean定义以及passwordEncoder
和daoAuthenticationProvider
覆盖。因为由插件使用Config.groovy
设置配置的bean就足够了。 另外,如果您已经将盐添加到User类的encodePassword
方法中,例如
protected void encodePassword() {
password = springSecurityService.encodePassword(password, username)
}
不用盐即可将其转换回默认值:
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}