Merge branch '5.8.x' into 6.0.x
Closes gh-13406
This commit is contained in:
@@ -67,26 +67,31 @@ Instead Spring Security introduces `DelegatingPasswordEncoder`, which solves all
|
||||
You can easily construct an instance of `DelegatingPasswordEncoder` by 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 can create your own custom instance:
|
||||
|
||||
.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
|
||||
----
|
||||
====
|
||||
|
||||
`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 `}`.
|
||||
@@ -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 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`
|
||||
@@ -182,12 +184,10 @@ In the `DelegatingPasswordEncoder` we constructed earlier, that means that the r
|
||||
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
|
||||
@@ -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"]
|
||||
----
|
||||
UserDetails 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 @@ UserDetails 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,26 +290,22 @@ The easiest way to properly encode your password is to use the https://docs.spri
|
||||
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`.
|
||||
|
||||
@@ -312,20 +314,16 @@ If you are migrating from Spring Security 4.2.x, you can revert to the previous
|
||||
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`].
|
||||
@@ -340,8 +338,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
|
||||
@@ -350,7 +350,8 @@ String result = encoder.encode("myPassword");
|
||||
assertTrue(encoder.matches("myPassword", result));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Create an encoder with strength 16
|
||||
@@ -358,7 +359,7 @@ val encoder = BCryptPasswordEncoder(16)
|
||||
val result: String = encoder.encode("myPassword")
|
||||
assertTrue(encoder.matches("myPassword", result))
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[authentication-password-storage-argon2]]
|
||||
== Argon2PasswordEncoder
|
||||
@@ -370,8 +371,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
|
||||
@@ -380,7 +383,8 @@ String result = encoder.encode("myPassword");
|
||||
assertTrue(encoder.matches("myPassword", result));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Create an encoder with all the defaults
|
||||
@@ -388,7 +392,7 @@ val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
|
||||
val result: String = encoder.encode("myPassword")
|
||||
assertTrue(encoder.matches("myPassword", result))
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[authentication-password-storage-pbkdf2]]
|
||||
== Pbkdf2PasswordEncoder
|
||||
@@ -399,8 +403,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
|
||||
@@ -409,7 +415,8 @@ String result = encoder.encode("myPassword");
|
||||
assertTrue(encoder.matches("myPassword", result));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Create an encoder with all the defaults
|
||||
@@ -417,7 +424,7 @@ val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
|
||||
val result: String = encoder.encode("myPassword")
|
||||
assertTrue(encoder.matches("myPassword", result))
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[authentication-password-storage-scrypt]]
|
||||
== SCryptPasswordEncoder
|
||||
@@ -427,8 +434,10 @@ To defeat password cracking on custom hardware, scrypt is a deliberately slow al
|
||||
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
|
||||
@@ -437,7 +446,8 @@ String result = encoder.encode("myPassword");
|
||||
assertTrue(encoder.matches("myPassword", result));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Create an encoder with all the defaults
|
||||
@@ -445,7 +455,7 @@ val encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
|
||||
val result: String = encoder.encode("myPassword")
|
||||
assertTrue(encoder.matches("myPassword", result))
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[authentication-password-storage-other]]
|
||||
== Other ``PasswordEncoder``s
|
||||
@@ -470,8 +480,10 @@ You should instead migrate to using `DelegatingPasswordEncoder` to support secur
|
||||
====
|
||||
|
||||
.NoOpPasswordEncoder
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@@ -480,14 +492,16 @@ public static NoOpPasswordEncoder 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
|
||||
@@ -495,7 +509,7 @@ fun passwordEncoder(): PasswordEncoder {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -513,36 +527,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
|
||||
@@ -551,13 +571,15 @@ http
|
||||
)
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:password-management change-password-page="/update-password"/>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
@@ -566,6 +588,6 @@ http {
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
With the above configuration, when a password manager navigates to `/.well-known/change-password`, then Spring Security will redirect to `/update-password`.
|
||||
|
||||
Reference in New Issue
Block a user