594 lines
24 KiB
Plaintext
594 lines
24 KiB
Plaintext
[[authentication-password-storage]]
|
|
= Password Storage
|
|
|
|
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.
|
|
|
|
[[authentication-password-storage-history]]
|
|
== Password Storage History
|
|
|
|
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.
|
|
|
|
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 https://en.wikipedia.org/wiki/Rainbow_table[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.
|
|
|
|
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.
|
|
|
|
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,bcrypt>>, <<authentication-password-storage-pbkdf2,PBKDF2>>, <<authentication-password-storage-scrypt,scrypt>>, and <<authentication-password-storage-argon2,argon2>>.
|
|
|
|
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.
|
|
|
|
|
|
[[authentication-password-storage-dpe]]
|
|
== DelegatingPasswordEncoder
|
|
|
|
Prior to Spring Security 5.0, the default `PasswordEncoder` was `NoOpPasswordEncoder`, which required plain-text passwords.
|
|
Based on the <<authentication-password-storage-history,Password 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.
|
|
|
|
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
|
|
|
|
You can easily construct an instance of `DelegatingPasswordEncoder` by using `PasswordEncoderFactories`:
|
|
|
|
.Create Default DelegatingPasswordEncoder
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
PasswordEncoder passwordEncoder =
|
|
PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
|
|
----
|
|
======
|
|
|
|
Alternatively, you can create your own custom instance:
|
|
|
|
.Create Custom DelegatingPasswordEncoder
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
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);
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
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)
|
|
----
|
|
======
|
|
|
|
[[authentication-password-storage-dpe-format]]
|
|
=== Password Storage Format
|
|
|
|
The general format for a password is:
|
|
|
|
.DelegatingPasswordEncoder Storage Format
|
|
[source,text,attrs="-attributes"]
|
|
----
|
|
{id}encodedPassword
|
|
----
|
|
|
|
`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
|
|
[source,text,attrs="-attributes"]
|
|
----
|
|
{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`
|
|
|
|
[NOTE]
|
|
====
|
|
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$`.
|
|
====
|
|
|
|
[[authentication-password-storage-dpe-encoding]]
|
|
=== Password Encoding
|
|
|
|
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
|
|
[source,text,attrs="-attributes"]
|
|
----
|
|
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
|
|
----
|
|
|
|
[[authentication-password-storage-dpe-matching]]
|
|
=== Password Matching
|
|
|
|
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,Password Storage 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)`.
|
|
|
|
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.
|
|
|
|
[[authentication-password-storage-dep-getting-started]]
|
|
=== 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
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary",attrs="-attributes"]
|
|
----
|
|
UserDetails user = User.withDefaultPasswordEncoder()
|
|
.username("user")
|
|
.password("password")
|
|
.roles("user")
|
|
.build();
|
|
System.out.println(user.getPassword());
|
|
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary",attrs="-attributes"]
|
|
----
|
|
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
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
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();
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
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()
|
|
----
|
|
======
|
|
|
|
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,hash your passwords externally>>.
|
|
|
|
[[authentication-password-storage-boot-cli]]
|
|
=== Encode with Spring Boot CLI
|
|
|
|
The easiest way to properly encode your password is to use the https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-cli.html[Spring Boot CLI].
|
|
|
|
For example, the following example encodes the password of `password` for use with <<authentication-password-storage-dpe>>:
|
|
|
|
.Spring Boot CLI encodepassword Example
|
|
[source,attrs="-attributes"]
|
|
----
|
|
spring encodepassword password
|
|
{bcrypt}$2a$10$X5wFBtLrL/kHcmrOGGTrGufsBX8CJ0WpQpF3pgeuxBB/H73BK1DW6
|
|
----
|
|
|
|
[[authentication-password-storage-dpe-troubleshoot]]
|
|
=== Troubleshooting
|
|
|
|
The following error occurs when one of the passwords that are stored has no `id`, as described in <<authentication-password-storage-dpe-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)
|
|
----
|
|
|
|
The easiest way to resolve it is to figure out how your passwords are currently being stored and explicitly provide the correct `PasswordEncoder`.
|
|
|
|
If you are migrating from Spring Security 4.2.x, you can revert to the previous behavior by <<authentication-password-storage-configuration,exposing a `NoOpPasswordEncoder` bean>>.
|
|
|
|
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
|
|
|
|
[source,attrs="-attributes"]
|
|
----
|
|
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
|
|
----
|
|
|
|
For a complete listing of the mappings, see the Javadoc for
|
|
https://docs.spring.io/spring-security/site/docs/5.0.x/api/org/springframework/security/crypto/factory/PasswordEncoderFactories.html[`PasswordEncoderFactories`].
|
|
|
|
[[authentication-password-storage-bcrypt]]
|
|
== BCryptPasswordEncoder
|
|
|
|
The `BCryptPasswordEncoder` implementation uses the widely supported https://en.wikipedia.org/wiki/Bcrypt[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 https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.html[`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
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
// Create an encoder with strength 16
|
|
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
|
|
String result = encoder.encode("myPassword");
|
|
assertTrue(encoder.matches("myPassword", result));
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
// Create an encoder with strength 16
|
|
val encoder = BCryptPasswordEncoder(16)
|
|
val result: String = encoder.encode("myPassword")
|
|
assertTrue(encoder.matches("myPassword", result))
|
|
----
|
|
======
|
|
|
|
[[authentication-password-storage-argon2]]
|
|
== Argon2PasswordEncoder
|
|
|
|
The `Argon2PasswordEncoder` implementation uses the https://en.wikipedia.org/wiki/Argon2[Argon2] algorithm to hash the passwords.
|
|
Argon2 is the winner of the https://en.wikipedia.org/wiki/Password_Hashing_Competition[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
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
// Create an encoder with all the defaults
|
|
Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
|
|
String result = encoder.encode("myPassword");
|
|
assertTrue(encoder.matches("myPassword", result));
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
// Create an encoder with all the defaults
|
|
val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
|
|
val result: String = encoder.encode("myPassword")
|
|
assertTrue(encoder.matches("myPassword", result))
|
|
----
|
|
======
|
|
|
|
[[authentication-password-storage-pbkdf2]]
|
|
== Pbkdf2PasswordEncoder
|
|
|
|
The `Pbkdf2PasswordEncoder` implementation uses the https://en.wikipedia.org/wiki/PBKDF2[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
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
// Create an encoder with all the defaults
|
|
Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
|
|
String result = encoder.encode("myPassword");
|
|
assertTrue(encoder.matches("myPassword", result));
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
// Create an encoder with all the defaults
|
|
val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
|
|
val result: String = encoder.encode("myPassword")
|
|
assertTrue(encoder.matches("myPassword", result))
|
|
----
|
|
======
|
|
|
|
[[authentication-password-storage-scrypt]]
|
|
== SCryptPasswordEncoder
|
|
|
|
The `SCryptPasswordEncoder` implementation uses the https://en.wikipedia.org/wiki/Scrypt[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
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
// Create an encoder with all the defaults
|
|
SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
|
|
String result = encoder.encode("myPassword");
|
|
assertTrue(encoder.matches("myPassword", result));
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
// Create an encoder with all the defaults
|
|
val encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
|
|
val result: String = encoder.encode("myPassword")
|
|
assertTrue(encoder.matches("myPassword", result))
|
|
----
|
|
======
|
|
|
|
[[authentication-password-storage-other]]
|
|
== Other ``PasswordEncoder``s
|
|
|
|
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.
|
|
|
|
[[authentication-password-storage-configuration]]
|
|
== Password Storage Configuration
|
|
|
|
Spring Security uses <<authentication-password-storage-dpe>> by default.
|
|
However, you can customize this by exposing a `PasswordEncoder` as a Spring bean.
|
|
|
|
|
|
If you are migrating from Spring Security 4.2.x, you can revert to the previous behavior by exposing a `NoOpPasswordEncoder` bean.
|
|
|
|
[WARNING]
|
|
====
|
|
Reverting to `NoOpPasswordEncoder` is not considered to be secure.
|
|
You should instead migrate to using `DelegatingPasswordEncoder` to support secure password encoding.
|
|
====
|
|
|
|
.NoOpPasswordEncoder
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@Bean
|
|
public static NoOpPasswordEncoder passwordEncoder() {
|
|
return NoOpPasswordEncoder.getInstance();
|
|
}
|
|
----
|
|
|
|
XML::
|
|
+
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<b:bean id="passwordEncoder"
|
|
class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
@Bean
|
|
fun passwordEncoder(): PasswordEncoder {
|
|
return NoOpPasswordEncoder.getInstance();
|
|
}
|
|
----
|
|
======
|
|
|
|
[NOTE]
|
|
====
|
|
XML Configuration requires the `NoOpPasswordEncoder` bean name to be `passwordEncoder`.
|
|
====
|
|
|
|
[[authentication-change-password-configuration]]
|
|
== Change Password Configuration
|
|
|
|
Most applications that allow a user to specify a password also require a feature for updating that password.
|
|
|
|
https://w3c.github.io/webappsec-change-password-url/[A Well-Known URL for Changing Passwords] indicates a mechanism by which password managers can discover the password update endpoint for a given application.
|
|
|
|
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
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
http
|
|
.passwordManagement(Customizer.withDefaults())
|
|
----
|
|
|
|
XML::
|
|
+
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<sec:password-management/>
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
http {
|
|
passwordManagement { }
|
|
}
|
|
----
|
|
======
|
|
|
|
Then, when a password manager navigates to `/.well-known/change-password` then Spring Security will redirect your endpoint, `/change-password`.
|
|
|
|
Or, if your endpoint is something other than `/change-password`, you can also specify that like so:
|
|
|
|
.Change Password Endpoint
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
http
|
|
.passwordManagement((management) -> management
|
|
.changePasswordPage("/update-password")
|
|
)
|
|
----
|
|
|
|
XML::
|
|
+
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<sec:password-management change-password-page="/update-password"/>
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
http {
|
|
passwordManagement {
|
|
changePasswordPage = "/update-password"
|
|
}
|
|
}
|
|
----
|
|
======
|
|
|
|
With the above configuration, when a password manager navigates to `/.well-known/change-password`, then Spring Security will redirect to `/update-password`.
|