Spring Security 中的密码编码器

x33g5p2x  于2022-09-15 转载在 Spring  
字(4.5k)|赞(0)|评价(0)|浏览(1054)

在这篇文章中,我们将通过一个示例详细了解密码编码器。传统上,存储密码很困难。应用程序必须对用户密码进行编码并将其存储在数据库中。

但是使用 spring security 提供的密码编码器,所有这些都可以自动完成。密码编码器是将纯文本密码转换为哈希的 bean。由于哈希值无法反转为明文,因此它是一种安全的密码存储方式。

密码哈希

首先,散列算法采用一系列字节并将其转换为唯一的固定长度散列字符串。

散列算法是单向函数,不能反转。意味着无法从散列中生成原始文本。

此特征可以使用散列存储密码。

Spring Security 密码编码器

对于密码编码/散列,Spring Security 需要一个密码编码器实现。此外,它还提供基于行业标准的实现。这些编码器将用于密码存储阶段和身份验证的验证阶段。

passwordEncoders 有两个重要任务。

  1. encoder.encode(String rawPassword) - 将给定的明文密码转换为编码密码。它如何转换取决于实现。这部分发生在密码存储在数据库中时。通常在注册用户或更改密码时。
  2. encoder.matches(rawPassword, encodedPassword) - 在登录时使用。安全上下文将从数据库中加载加密密码,并使用此方法与原始密码进行比较。

以下是编码器在注册过程中的实现。

下图说明了 Spring Security 如何使用编码器来验证登录密码。

现在让我们看一些例子。

使用密码编码器注册

在注册时,您需要对密码进行编码,然后再将其存储到数据库中。为此,您需要定义一个类型为 PasswordEncoder 的 bean。

@Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
Code language: JavaScript (javascript)

在你的注册 API的地方,你必须自动装配这个 bean。如下

@RestController
public class RegistrationController {


    private final UserAccountRepository userAccountRepository;
    private final PasswordEncoder passwordEncoder;

    public UserController(UserAccountRepository userAccountRepository, PasswordEncoder passwordEncoder) {
        this.userAccountRepository = userAccountRepository;
        this.passwordEncoder = passwordEncoder;
    }


    @PostMapping("/register")
    public UserAccount register(@RequestParam("username") String username, @RequestParam("password") String password, @RequestParam("firstName") String firstName, @RequestParam("lastName") String lastName) {
        UserAccount userAccount = new UserAccount();
        userAccount.setFirstName(firstName);
        userAccount.setLastName(lastName);
        userAccount.setUsername(username);
        userAccount.setActive(true);
        userAccount.setPassword(passwordEncoder.encode(password));
        return userAccountRepository.save(userAccount);
    }
}
Code language: PHP (php)

您可以在本文末尾查看 git 存储库以获取完整实现。在此示例中,我们在 BCryptPasswordEncoder 的帮助下注册了用户。您可以在下图中看到密码是如何编码的。

从 Spring Security 4 迁移密码

如果您是从 spring security4 升级,那么您需要确保当前密码使用相同的算法进行加密。这是将明文密码转换为哈希的示例代码。

public class BCryptConverter {
    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        System.out.println(bCryptPasswordEncoder.encode("[email protected]"));
        System.out.println(bCryptPasswordEncoder.encode("Hello#123"));
    }
}
Code language: JavaScript (javascript)

委托密码编码器

如果您想在同一个数据源中使用多种类型的编码器,可能会出现这种情况。例如,MD5、SHA-256、pbkdf2 是一些常见的密码散列函数。出于这个原因,spring 提供了一个 DelegatingPasswordEncoder

此编码器通过基于密码前缀路由请求来依赖其他密码编码器。要使用它,您需要对我们之前的代码进行一些更改。

您需要为类型 DelegatingPasswordEncoder 而不是 BCryptPasswordEncoder 创建 bean,您可以在 PasswordEncoderFactories 类的帮助下轻松完成此操作。

@Bean
    PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
Code language: CSS (css)

默认情况下,此委托编码器使用 bcrypt 算法进行编码。这就是为什么存储在数据库中的密码会在前面加上文本 {bcrypt}。当调用 encoder.matches() 方法时,此附加信息将用于识别适当的 passwordEncoder。

编码类型的默认映射如下所示。

编码实现匹配
bcryptnew org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder();
ldapnew org.springframework.security.crypto.password.LdapShaPasswordEncoder();
MD4new org.springframework.security.crypto.password.Md4PasswordEncoder();
MD5new org.springframework.security.crypto.password.MessageDigestPasswordEncoder(“MD5”);
noopnew org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance();
pbkdf2new org.springframework.security.crypto.password.Pbkdf2PasswordEncoder();
scryptnew org.springframework.security.crypto.scrypt.SCryptPasswordEncoder();
SHA-1new org.springframework.security.crypto.password.MessageDigestPasswordEncoder(“SHA-1”);
SHA-256new org.springframework.security.crypto.password.MessageDigestPasswordEncoder(“SHA-256”);
sha256new org.springframework.security.crypto.password.StandardPasswordEncoder();
argon2new org.springframework.security.crypto.argon2.Argon2PasswordEncoder();

自定义 DelegatingPasswordEncoder

您可以通过自己创建 DelegatingPasswordEncoder 来自定义支持的编码类型列表。如下所示

@Bean
    PasswordEncoder passwordEncoder() {
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        encoders.put("bcrypt", new BCryptPasswordEncoder());
        encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
        return new DelegatingPasswordEncoder("bcrypt", encoders);
    }
Code language: JavaScript (javascript)

将旧的明文密码迁移到 bcyrpt

委托编码器将允许编码密码和纯文本密码共存。 但是,那些未编码的密码仍然存在安全风险。 在这些情况下,编写一个程序,将所有纯文本密码转换为编码字符串。 示例如下

public class BCryptConvert {
    public static void main(String[] args) {
        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        System.out.println(passwordEncoder.encode("[email protected]"));
        System.out.println(passwordEncoder.encode("Hello#123"));
    }
}
Code language: JavaScript (javascript)

GitHub

您可以在GitHub repository 找到这些示例。

相关文章