Merge branch '5.7.x' into 5.8.x

Closes gh-13405
This commit is contained in:
Rob Winch
2023-06-18 21:32:35 -05:00
108 changed files with 5712 additions and 3422 deletions

View File

@@ -67,26 +67,31 @@ Instead Spring Security introduces `DelegatingPasswordEncoder` which solves all
You can easily construct an instance of `DelegatingPasswordEncoder` using `PasswordEncoderFactories`.
.Create Default DelegatingPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
----
====
======
Alternatively, you may create your own custom instance. For example:
.Create Custom DelegatingPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
String idForEncode = "bcrypt";
@@ -105,7 +110,8 @@ PasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(idForEncode, encoders);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val idForEncode = "bcrypt"
@@ -122,7 +128,7 @@ encoders["sha256"] = StandardPasswordEncoder()
val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, encoders)
----
====
======
[[authentication-password-storage-dpe-format]]
=== Password Storage Format
@@ -130,12 +136,10 @@ val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, en
The general format for a password is:
.DelegatingPasswordEncoder Storage Format
====
[source,text,attrs="-attributes"]
----
{id}encodedPassword
----
====
Such that `id` is an identifier 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 `}`.
@@ -144,7 +148,6 @@ For example, the following might be a list of passwords encoded using different
All of the original passwords are "password".
.DelegatingPasswordEncoder Encoded Passwords Example
====
[source,text,attrs="-attributes"]
----
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG // <1>
@@ -153,7 +156,6 @@ All of the original passwords are "password".
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= // <4>
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 // <5>
----
====
<1> The first password would have a `PasswordEncoder` id of `bcrypt` and encodedPassword of `$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG`.
When matching it would delegate to `BCryptPasswordEncoder`
@@ -182,12 +184,10 @@ In the `DelegatingPasswordEncoder` we constructed above, that means that the res
The end result would look like:
.DelegatingPasswordEncoder Encode Example
====
[source,text,attrs="-attributes"]
----
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
====
[[authentication-password-storage-dpe-matching]]
=== Password Matching
@@ -209,8 +209,10 @@ If you are putting together a demo or a sample, it is a bit cumbersome to take t
There are convenience mechanisms to make this easier, but this is still not intended for production.
.withDefaultPasswordEncoder Example
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary",attrs="-attributes"]
----
User user = User.withDefaultPasswordEncoder()
@@ -222,7 +224,8 @@ System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary",attrs="-attributes"]
----
val user = User.withDefaultPasswordEncoder()
@@ -233,13 +236,15 @@ val user = User.withDefaultPasswordEncoder()
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
[tabs]
======
Java::
+
[source,java,role="primary"]
----
UserBuilder users = User.withDefaultPasswordEncoder();
@@ -255,7 +260,8 @@ User admin = users
.build();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val users = User.withDefaultPasswordEncoder()
@@ -270,7 +276,7 @@ val admin = users
.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.
@@ -284,13 +290,11 @@ The easiest way to properly encode your password is to use the https://docs.spri
For example, the following will encode 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
@@ -336,8 +340,10 @@ The default implementation of `BCryptPasswordEncoder` uses strength 10 as mentio
tune and test the strength parameter on your own system so that it takes roughly 1 second to verify a password.
.BCryptPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with strength 16
@@ -346,7 +352,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with strength 16
@@ -354,7 +361,7 @@ val encoder = BCryptPasswordEncoder(16)
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-argon2]]
== Argon2PasswordEncoder
@@ -366,8 +373,10 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second
The current implementation of the `Argon2PasswordEncoder` requires BouncyCastle.
.Argon2PasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -376,7 +385,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -384,7 +394,7 @@ val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-pbkdf2]]
== Pbkdf2PasswordEncoder
@@ -395,8 +405,10 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second
This algorithm is a good choice when FIPS certification is required.
.Pbkdf2PasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -405,7 +417,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -413,7 +426,7 @@ val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-scrypt]]
== SCryptPasswordEncoder
@@ -423,8 +436,10 @@ In order to defeat password cracking on custom hardware scrypt is a deliberately
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
.SCryptPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -433,7 +448,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -441,7 +457,7 @@ val encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-other]]
== Other PasswordEncoders
@@ -466,8 +482,10 @@ You should instead migrate to using `DelegatingPasswordEncoder` to support secur
====
.NoOpPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -476,14 +494,16 @@ public static PasswordEncoder passwordEncoder() {
}
----
.XML
XML::
+
[source,xml,role="secondary"]
----
<b:bean id="passwordEncoder"
class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -491,7 +511,7 @@ fun passwordEncoder(): PasswordEncoder {
return NoOpPasswordEncoder.getInstance();
}
----
====
======
[NOTE]
====
@@ -509,36 +529,42 @@ 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
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
.passwordManagement(Customizer.withDefaults())
----
.XML
XML::
+
[source,xml,role="secondary"]
----
<sec:password-management/>
----
.Kotlin
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
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
@@ -547,13 +573,15 @@ http
)
----
.XML
XML::
+
[source,xml,role="secondary"]
----
<sec:password-management change-password-page="/update-password"/>
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
@@ -562,6 +590,6 @@ http {
}
}
----
====
======
With the above configuration, when a password manager navigates to `/.well-known/change-password`, then Spring Security will redirect to `/update-password`.