Password Storage

Spring Security 的 PasswordEncoder 接口用于对密码执行单向转换,使密码可以安全地存储。鉴于 PasswordEncoder 是单向转换,因此当密码转换需要是双向的(例如存储用于对数据库进行身份验证的凭据)时,它将没有用。通常,PasswordEncoder 用于存储在身份验证时需要与用户提供的密码进行比较的密码。

Spring Security’s PasswordEncoder interface is used to perform a one-way transformation of a password to let the password be stored securely. Given PasswordEncoder is a one-way transformation, it is not useful when the password transformation needs to be two-way (such as storing credentials used to authenticate to a database). Typically, PasswordEncoder is used for storing a password that needs to be compared to a user-provided password at the time of authentication.

Password Storage History

多年来,存储密码的标准机制一直在不断发展。最初,密码以纯文本形式存储。人们认为密码是安全的,因为存储密码的数据存储需要访问该信息。然而,恶意用户能够使用 SQL 注入等攻击找到包含大量 “data dumps” 的用户名和密码的方法。随着越来越多的用户凭据公开,安全专家意识到我们需要采取更多措施来保护用户的密码。

Throughout the years, the standard mechanism for storing passwords has evolved. In the beginning, passwords were stored in plaintext. The passwords were assumed to be safe because the data store the passwords were saved in required credentials to access it. However, malicious users were able to find ways to get large “data dumps” of usernames and passwords by using attacks such as SQL Injection. As more and more user credentials became public, security experts realized that we needed to do more to protect users' passwords.

然后鼓励开发人员在通过单向哈希(如SHA-256)运行密码后将其存储起来。当用户尝试进行身份验证时,将哈希密码与他们键入的密码的哈希进行比较。这意味着该系统只需要存储密码的单向哈希。如果发生泄露,则仅泄露密码的单向哈希。由于哈希是单向的,并且根据哈希猜测密码在计算上很困难,因此不值得费力去找出系统中的每个密码。为了击败这个新系统,恶意用户决定创建称为 Rainbow Tables的查找表。他们并没有每次猜测每个密码,而是计算一次密码并将其存储在查找表中。

Developers were then encouraged to store passwords after running them through a one way hash, such as SHA-256. When a user tried to authenticate, the hashed password would be compared to the hash of the password that they typed. This meant that the system only needed to store the one-way hash of the password. If a breach occurred, only the one-way hashes of the passwords were exposed. Since the hashes were one-way and it was computationally difficult to guess the passwords given the hash, it would not be worth the effort to figure out each password in the system. To defeat this new system, malicious users decided to create lookup tables known as Rainbow Tables. Rather than doing the work of guessing each password every time, they computed the password once and stored it in a lookup table.

为了减轻彩虹表的有效性,鼓励开发人员使用加盐密码。不是仅将密码用作哈希函数的输入,而是为每位用户的密码生成随机字节(称为盐)。盐和用户密码将通过哈希函数运行以生成唯一的哈希。该盐将与用户的密码一起以明文形式存储。然后,当用户尝试进行身份验证时,哈希密码将与所存储的盐和他们键入的密码的哈希进行比较。唯一的盐意味着彩虹表不再有效,因为每个盐和密码组合的哈希不同。

To mitigate the effectiveness of Rainbow Tables, developers were encouraged to use salted passwords. Instead of using just the password as input to the hash function, random bytes (known as salt) would be generated for every user’s password. The salt and the user’s password would be run through the hash function to produce a unique hash. The salt would be stored alongside the user’s password in clear text. Then when a user tried to authenticate, the hashed password would be compared to the hash of the stored salt and the password that they typed. The unique salt meant that Rainbow Tables were no longer effective because the hash was different for every salt and password combination.

在现代,我们意识到加密哈希(如 SHA-256)不再安全。原因是,使用现代硬件,我们每秒可以执行数十亿次哈希计算。这意味着我们可以轻松地破解每个密码。

In modern times, we realize that cryptographic hashes (like SHA-256) are no longer secure. The reason is that with modern hardware we can perform billions of hash calculations a second. This means that we can crack each password individually with ease.

现在鼓励开发人员利用自适应单向函数存储密码。通过自适应单向函数验证密码有意地资源密集(它们有意识地使用了大量的 CPU、内存或其他资源)。自适应单向函数允许配置 “work factor”,该配置可以随着硬件的改进而增长。我们建议将 “work factor” 调整为在您的系统上验证密码大约需要一秒钟。这种权衡是为了让攻击者难以破解密码,但不要造成太大的成本,以免对您自己的系统造成过大的负担或激怒用户。Spring Security 已尝试为 “work factor” 提供一个良好的起点,但我们鼓励用户为自己的系统定制 “work factor”,因为性能因系统而异很大。应该使用的自适应单向函数的示例包括 bcryptPBKDF2scryptargon2

Developers are now encouraged to leverage adaptive one-way functions to store a password. Validation of passwords with adaptive one-way functions are intentionally resource-intensive (they intentionally use a lot of CPU, memory, or other resources). An adaptive one-way function allows configuring a “work factor” that can grow as hardware gets better. We recommend that the “work factor” be tuned to take about one second to verify a password on your system. This trade off is to make it difficult for attackers to crack the password, but not so costly that it puts excessive burden on your own system or irritates users. Spring Security has attempted to provide a good starting point for the “work factor”, but we encourage users to customize the “work factor” for their own system, since the performance varies drastically from system to system. Examples of adaptive one-way functions that should be used include authentication-password-storage-bcrypt, authentication-password-storage-pbkdf2, authentication-password-storage-scrypt, and authentication-password-storage-argon2.

因为自适应单向函数有意地资源密集,因此为每个请求验证用户名和密码会显着降低应用程序的性能。Spring Security(或任何其他库)无法加快密码验证速度,因为安全性是通过使验证资源密集来获得的。鼓励用户将长期凭据(即用户名和密码)换成短期凭据(如会话和 OAuth 令牌等)。短期凭据可以快速验证,而不会损失安全性。

Because adaptive one-way functions are intentionally resource intensive, validating a username and password for every request can significantly degrade the performance of an application. There is nothing Spring Security (or any other library) can do to speed up the validation of the password, since security is gained by making the validation resource intensive. Users are encouraged to exchange the long term credentials (that is, username and password) for a short term credential (such as a session, and OAuth Token, and so on). The short term credential can be validated quickly without any loss in security.

DelegatingPasswordEncoder

在 Spring Security 5.0 之前,默认 PasswordEncoderNoOpPasswordEncoder,它需要纯文本密码。根据 Password History 部分,您可能希望默认 PasswordEncoder 现在类似 BCryptPasswordEncoder。但是,这忽略了三个现实世界的问题:

Prior to Spring Security 5.0, the default PasswordEncoder was NoOpPasswordEncoder, which required plain-text passwords. Based on the authentication-password-storage-history section, you might expect that the default PasswordEncoder would now be something like BCryptPasswordEncoder. However, this ignores three real world problems:

  • Many applications use old password encodings that cannot easily migrate.

  • The best practice for password storage will change again.

  • As a framework, Spring Security cannot make breaking changes frequently.

相反,Spring Security 引入了 DelegatingPasswordEncoder,它通过以下方式解决了所有问题:

Instead Spring Security introduces DelegatingPasswordEncoder, which solves all of the problems by:

  • Ensuring that passwords are encoded by using the current password storage recommendations

  • Allowing for validating passwords in modern and legacy formats

  • Allowing for upgrading the encoding in the future

您可以通过使用 PasswordEncoderFactories 轻松构建 DelegatingPasswordEncoder 的实例:

You can easily construct an instance of DelegatingPasswordEncoder by using PasswordEncoderFactories:

Create Default DelegatingPasswordEncoder
  • Java

  • Kotlin

PasswordEncoder passwordEncoder =
    PasswordEncoderFactories.createDelegatingPasswordEncoder();
val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()

或者,您可以创建自己的自定义实例:

Alternatively, you can create your own custom instance:

Create Custom DelegatingPasswordEncoder
  • Java

  • Kotlin

String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("sha256", new StandardPasswordEncoder());

PasswordEncoder passwordEncoder =
    new DelegatingPasswordEncoder(idForEncode, encoders);
val idForEncode = "bcrypt"
val encoders: MutableMap<String, PasswordEncoder> = mutableMapOf()
encoders[idForEncode] = BCryptPasswordEncoder()
encoders["noop"] = NoOpPasswordEncoder.getInstance()
encoders["pbkdf2"] = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5()
encoders["pbkdf2@SpringSecurity_v5_8"] = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
encoders["scrypt"] = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1()
encoders["scrypt@SpringSecurity_v5_8"] = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
encoders["argon2"] = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2()
encoders["argon2@SpringSecurity_v5_8"] = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
encoders["sha256"] = StandardPasswordEncoder()

val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, encoders)

Password Storage Format

密码的一般格式为:

The general format for a password is:

DelegatingPasswordEncoder Storage Format
{id}encodedPassword

id 是一个标识符,用于查找应该使用哪个 PasswordEncoder,而 encodedPassword 是所选 PasswordEncoder 的原始编码密码。id 必须在密码的开头,以 { 开头,并以 } 结尾。如果找不到 id,则 id 设置为 null。例如,以下可能是使用不同 id 值编码的密码的列表。所有原始密码都是 password

id is an identifier that is used to look up which PasswordEncoder should be used and encodedPassword is the original encoded password for the selected PasswordEncoder. The id must be at the beginning of the password, start with {, and end with }. If the id cannot be found, the id is set to null. For example, the following might be a list of passwords encoded using different id values. All of the original passwords are password.

DelegatingPasswordEncoder Encoded Passwords Example
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG (1)
{noop}password (2)
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc (3)
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=  (4)
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 (5)
1 The first password has a PasswordEncoder id of bcrypt and an encodedPassword value of $2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG. When matching, it would delegate to BCryptPasswordEncoder
2 The second password has a PasswordEncoder id of noop and encodedPassword value of password. When matching, it would delegate to NoOpPasswordEncoder
3 The third password has a PasswordEncoder id of pbkdf2 and encodedPassword value of 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc. When matching, it would delegate to Pbkdf2PasswordEncoder
4 The fourth password has a PasswordEncoder id of scrypt and encodedPassword value of $e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= When matching, it would delegate to SCryptPasswordEncoder
5 The final password has a PasswordEncoder id of sha256 and encodedPassword value of 97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0. When matching, it would delegate to StandardPasswordEncoder

一些用户可能担心为潜在的黑客提供了存储格式。这不是问题,因为密码的存储并不依赖于算法是秘密。此外,大多数格式很容易让攻击者在不使用前缀的情况下找出。例如,BCrypt 密码通常以 $2a$ 开头。

Some users might be concerned that the storage format is provided for a potential hacker. This is not a concern because the storage of the password does not rely on the algorithm being a secret. Additionally, most formats are easy for an attacker to figure out without the prefix. For example, BCrypt passwords often start with $2a$.

Password Encoding

传入构造函数的 idForEncode 确定哪个 PasswordEncoder 用于编码密码。在我们之前构建的 DelegatingPasswordEncoder 中,这意味着对 password 进行编码的结果委托给 BCryptPasswordEncoder,并以 {bcrypt} 为前缀。最终结果如下例所示:

The idForEncode passed into the constructor determines which PasswordEncoder is used for encoding passwords. In the DelegatingPasswordEncoder we constructed earlier, that means that the result of encoding password is delegated to BCryptPasswordEncoder and be prefixed with {bcrypt}. The end result looks like the following example:

DelegatingPasswordEncoder Encode Example
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

Password Matching

匹配基于 {id} 以及在构造函数中提供的 idPasswordEncoder 的映射。我们在 Password Storage Format 中的示例提供了一个关于如何完成此操作的工作示例。默认情况下,使用未映射的密码和 id(包括 null id)调用 matches(CharSequence, String) 的结果将导致 IllegalArgumentException。可以使用 DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder) 自定义此行为。

Matching is based upon the {id} and the mapping of the id to the PasswordEncoder provided in the constructor. Our example in authentication-password-storage-dpe-format provides a working example of how this is done. By default, the result of invoking matches(CharSequence, String) with a password and an id that is not mapped (including a null id) results in an IllegalArgumentException. This behavior can be customized by using DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder).

通过使用 id,我们可以匹配任何密码编码,但使用最现代的密码编码对密码进行编码。这一点很重要,因为与加密不同,密码哈希被设计为没有简单的方法可以恢复明文。由于没有办法恢复明文,因此难以迁移密码。虽然用户可以轻松地迁移 NoOpPasswordEncoder,但我们选择默认包含它,以简化入门体验。

By using the id, we can match on any password encoding but encode passwords by using the most modern password encoding. This is important, because unlike encryption, password hashes are designed so that there is no simple way to recover the plaintext. Since there is no way to recover the plaintext, it is difficult to migrate the passwords. While it is simple for users to migrate NoOpPasswordEncoder, we chose to include it by default to make it simple for the getting-started experience.

Getting Started Experience

如果您正在制作演示或示例,则花时间为用户密码进行哈希有点麻烦。有一些方便的机制可以简化此操作,但这仍然不适用于生产。

If you are putting together a demo or a sample, it is a bit cumbersome to take time to hash the passwords of your users. There are convenience mechanisms to make this easier, but this is still not intended for production.

withDefaultPasswordEncoder Example
  • Java

  • Kotlin

UserDetails user = User.withDefaultPasswordEncoder()
  .username("user")
  .password("password")
  .roles("user")
  .build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
val user = User.withDefaultPasswordEncoder()
    .username("user")
    .password("password")
    .roles("user")
    .build()
println(user.password)
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

如果您正在创建多个用户,您还可以重复使用构建器:

If you are creating multiple users, you can also reuse the builder:

withDefaultPasswordEncoder Reusing the Builder
  • Java

  • Kotlin

UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
  .username("user")
  .password("password")
  .roles("USER")
  .build();
UserDetails admin = users
  .username("admin")
  .password("password")
  .roles("USER","ADMIN")
  .build();
val users = User.withDefaultPasswordEncoder()
val user = users
    .username("user")
    .password("password")
    .roles("USER")
    .build()
val admin = users
    .username("admin")
    .password("password")
    .roles("USER", "ADMIN")
    .build()

这会对存储的密码进行哈希处理,但密码仍暴露在内存和已编译的源代码中。因此,对于生产环境而言,它仍然不被认为是安全的。对于生产,您应该 hash your passwords externally

This does hash the password that is stored, but the passwords are still exposed in memory and in the compiled source code. Therefore, it is still not considered secure for a production environment. For production, you should authentication-password-storage-boot-cli.

Encode with Spring Boot CLI

正确编码密码的最简单的方法是使用 Spring Boot CLI

The easiest way to properly encode your password is to use the Spring Boot CLI.

例如,以下示例对 password 的密码进行编码以用于 DelegatingPasswordEncoder

For example, the following example encodes the password of password for use with DelegatingPasswordEncoder:

Spring Boot CLI encodepassword Example
spring encodepassword password
{bcrypt}$2a$10$X5wFBtLrL/kHcmrOGGTrGufsBX8CJ0WpQpF3pgeuxBB/H73BK1DW6

Troubleshooting

如果所存储的密码之一没有 id,则会发生以下错误,如 Password Storage Format 中所述。

The following error occurs when one of the passwords that are stored has no id, as described in Password Storage Format.

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
	at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
	at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)

解决它的最简单方法是弄清楚你的密码当前是如何存储的,然后明确地提供正确的 PasswordEncoder

The easiest way to resolve it is to figure out how your passwords are currently being stored and explicitly provide the correct PasswordEncoder.

如果你从 Spring Security 4.2.x 迁移过来,你可以通过 exposing a NoOpPasswordEncoder bean 来恢复到之前的行为。

If you are migrating from Spring Security 4.2.x, you can revert to the previous behavior by authentication-password-storage-configuration.

或者,你可以用正确的 id 给所有密码加前缀,然后继续使用 DelegatingPasswordEncoder。例如,如果你使用 BCrypt,你可以将你的密码从类似这样的字符串迁移过来:

Alternatively, you can prefix all of your passwords with the correct id and continue to use DelegatingPasswordEncoder. For example, if you are using BCrypt, you would migrate your password from something like:

$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

更改为

to

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

有关映射的完整列表,请参阅 link:https://docs.spring.io/spring-security/site/docs/5.0.x/api/org/springframework/security/crypto/factory/PasswordEncoderFactories.html[PasswordEncoderFactories 的 Javadoc。

For a complete listing of the mappings, see the Javadoc for PasswordEncoderFactories.

BCryptPasswordEncoder

BCryptPasswordEncoder`实现使用广泛支持的 bcrypt算法对密码进行哈希处理。为了增强其抗密码破解能力,bcrypt 故意设计的较慢。与其他自适应单向函数类似,它应该调整为在大约 1 秒钟内完成系统上密码的验证。`BCryptPasswordEncoder`的默认实现使用 `BCryptPasswordEncoder的 Javadoc 中提到的强度 10。建议您对自己的系统微调并测试强度参数,以便在大约 1 秒钟内验证密码。

The BCryptPasswordEncoder implementation uses the widely supported bcrypt algorithm to hash the passwords. To make it more resistant to password cracking, bcrypt is deliberately slow. Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system. The default implementation of BCryptPasswordEncoder uses strength 10 as mentioned in the Javadoc of BCryptPasswordEncoder. You are encouraged to tune and test the strength parameter on your own system so that it takes roughly 1 second to verify a password.

BCryptPasswordEncoder
  • Java

  • Kotlin

// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
// Create an encoder with strength 16
val encoder = BCryptPasswordEncoder(16)
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))

Argon2PasswordEncoder

`Argon2PasswordEncoder`实现使用 Argon2算法对密码进行哈希处理。Argon2 是 Password Hashing Competition的获胜者。为了在定制硬件上阻止密码破解,Argon2 是一种故意设计得较慢的算法,需要大量的内存。与其他自适应单向函数类似,它应该调整为在大约 1 秒钟内完成系统上密码的验证。`Argon2PasswordEncoder`的当前实现需要 BouncyCastle。

The Argon2PasswordEncoder implementation uses the Argon2 algorithm to hash the passwords. Argon2 is the winner of the Password Hashing Competition. To defeat password cracking on custom hardware, Argon2 is a deliberately slow algorithm that requires large amounts of memory. Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system. The current implementation of the Argon2PasswordEncoder requires BouncyCastle.

Argon2PasswordEncoder
  • Java

  • Kotlin

// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
// Create an encoder with all the defaults
val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))

Pbkdf2PasswordEncoder

`Pbkdf2PasswordEncoder`实现使用 PBKDF2算法对密码进行哈希。为了阻止 PBKDF2 的密码破解,这是一种故意设计得较慢的算法。与其他自适应单向函数类似,它应该调整为在大约 1 秒钟内完成系统上密码的验证。当要求 FIPS 认证时,此算法是一个不错的选择。

The Pbkdf2PasswordEncoder implementation uses the PBKDF2 algorithm to hash the passwords. To defeat password cracking PBKDF2 is a deliberately slow algorithm. Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system. This algorithm is a good choice when FIPS certification is required.

Pbkdf2PasswordEncoder
  • Java

  • Kotlin

// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
// Create an encoder with all the defaults
val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))

SCryptPasswordEncoder

`SCryptPasswordEncoder`实现使用 scrypt算法对密码进行哈希。为了在定制硬件上阻止密码破解,scrypt 是一种故意设计得较慢的算法,需要大量的内存。与其他自适应单向函数类似,它应该调整为在大约 1 秒钟内完成系统上密码的验证。

The SCryptPasswordEncoder implementation uses the scrypt algorithm to hash the passwords. To defeat password cracking on custom hardware, scrypt is a deliberately slow algorithm that requires large amounts of memory. Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.

SCryptPasswordEncoder
  • Java

  • Kotlin

// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
// Create an encoder with all the defaults
val encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))

Other `PasswordEncoder`s

还有大量其他 PasswordEncoder 实现完全是为了向后兼容。它们都被标记为已弃用,以表明它们不再被认为是安全的。但是,我们没有计划删除它们,因为迁移现有的旧系统很困难。

There are a significant number of other PasswordEncoder implementations that exist entirely for backward compatibility. They are all deprecated to indicate that they are no longer considered secure. However, there are no plans to remove them, since it is difficult to migrate existing legacy systems.

Password Storage Configuration

默认情况下,Spring Security 使用 DelegatingPasswordEncoder。但是,你可以通过将 PasswordEncoder 公开为 Spring bean 来自定义它。

Spring Security uses DelegatingPasswordEncoder by default. However, you can customize this by exposing a PasswordEncoder as a Spring bean.

如果你从 Spring Security 4.2.x 迁移过来,你可以通过公开一个 NoOpPasswordEncoder bean 来恢复到之前的行为。

If you are migrating from Spring Security 4.2.x, you can revert to the previous behavior by exposing a NoOpPasswordEncoder bean.

恢复到 NoOpPasswordEncoder 被认为是不安全的。你应该迁移到使用 DelegatingPasswordEncoder 以支持安全的密码编码。

Reverting to NoOpPasswordEncoder is not considered to be secure. You should instead migrate to using DelegatingPasswordEncoder to support secure password encoding.

NoOpPasswordEncoder
  • Java

  • XML

  • Kotlin

@Bean
public static NoOpPasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}
<b:bean id="passwordEncoder"
        class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
@Bean
fun passwordEncoder(): PasswordEncoder {
    return NoOpPasswordEncoder.getInstance();
}

XML 配置要求 NoOpPasswordEncoder bean 名称是 passwordEncoder

XML Configuration requires the NoOpPasswordEncoder bean name to be passwordEncoder.

Change Password Configuration

大多数允许用户指定密码的应用程序还需要一个用于更新该密码的功能。

Most applications that allow a user to specify a password also require a feature for updating that password.

A Well-Known URL for Changing Passwords表示密码管理器可以发现给定应用程序的密码更新端点的机制。

A Well-Known URL for Changing Passwords indicates a mechanism by which password managers can discover the password update endpoint for a given application.

你可以配置 Spring Security 来提供此发现端点。例如,如果你的应用程序中的更改密码端点是 /change-password,那么你可以像这样配置 Spring Security:

You can configure Spring Security to provide this discovery endpoint. For example, if the change password endpoint in your application is /change-password, then you can configure Spring Security like so:

Default Change Password Endpoint
  • Java

  • XML

  • Kotlin

http
    .passwordManagement(Customizer.withDefaults())
<sec:password-management/>
http {
    passwordManagement { }
}

然后,当密码管理器导航到 /.well-known/change-password 时,Spring Security 将重定向你的端点 /change-password

Then, when a password manager navigates to /.well-known/change-password then Spring Security will redirect your endpoint, /change-password.

或者,如果你的端点不是 /change-password,你也可以像这样指定:

Or, if your endpoint is something other than /change-password, you can also specify that like so:

Change Password Endpoint
  • Java

  • XML

  • Kotlin

http
    .passwordManagement((management) -> management
        .changePasswordPage("/update-password")
    )
<sec:password-management change-password-page="/update-password"/>
http {
    passwordManagement {
        changePasswordPage = "/update-password"
    }
}

使用上面的配置,当密码管理器导航到 /.well-known/change-password 时,Spring Security 将重定向到 /update-password

With the above configuration, when a password manager navigates to /.well-known/change-password, then Spring Security will redirect to /update-password.