diff --git a/docs/modules/ROOT/pages/features/authentication/password-storage.adoc b/docs/modules/ROOT/pages/features/authentication/password-storage.adoc index 94e2cf21e03..6e2ab210645 100644 --- a/docs/modules/ROOT/pages/features/authentication/password-storage.adoc +++ b/docs/modules/ROOT/pages/features/authentication/password-storage.adoc @@ -67,68 +67,12 @@ Instead Spring Security introduces `DelegatingPasswordEncoder`, which solves all 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() ----- -====== +include-code::./DelegatingPasswordEncoderUsage[tag=createDefaultPasswordEncoder,indent=0] 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 = 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) ----- -====== +include-code::./DelegatingPasswordEncoderUsage[tag=createCustomPasswordEncoder,indent=0] [[authentication-password-storage-dpe-format]] === Password Storage Format @@ -209,74 +153,12 @@ 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 -[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 ----- -====== +include-code::./WithDefaultPasswordEncoderUsage[tag=createSingleUser,indent=0] 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() ----- -====== +include-code::./WithDefaultPasswordEncoderUsage[tag=createMultipleUsers,indent=0] 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. @@ -337,28 +219,7 @@ 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 -[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)) ----- -====== +include-code::./BCryptPasswordEncoderUsage[tag=bcryptPasswordEncoder,indent=0] [[authentication-password-storage-argon2]] == Argon2PasswordEncoder @@ -370,28 +231,7 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second 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)) ----- -====== +include-code::./Argon2PasswordEncoderUsage[tag=argon2PasswordEncoder,indent=0] [[authentication-password-storage-pbkdf2]] == Pbkdf2PasswordEncoder @@ -402,28 +242,7 @@ 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 -[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)) ----- -====== +include-code::./Pbkdf2PasswordEncoderUsage[tag=pbkdf2PasswordEncoder,indent=0] [[authentication-password-storage-scrypt]] == SCryptPasswordEncoder @@ -433,28 +252,7 @@ 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 -[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)) ----- -====== +include-code::./SCryptPasswordEncoderUsage[tag=sCryptPasswordEncoder,indent=0] [[authentication-password-storage-other]] == Other ``PasswordEncoder``s @@ -606,86 +404,4 @@ However, just a 401 or the redirect is not so useful in that case, it will cause In such cases, you can handle the `CompromisedPasswordException` via the `AuthenticationFailureHandler` to perform your desired logic, like redirecting the user-agent to `/reset-password`, for example: .Using CompromisedPasswordChecker -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@Bean -public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests(authorize -> authorize - .anyRequest().authenticated() - ) - .formLogin((login) -> login - .failureHandler(new CompromisedPasswordAuthenticationFailureHandler()) - ); - return http.build(); -} - -@Bean -public CompromisedPasswordChecker compromisedPasswordChecker() { - return new HaveIBeenPwnedRestApiPasswordChecker(); -} - -static class CompromisedPasswordAuthenticationFailureHandler implements AuthenticationFailureHandler { - - private final SimpleUrlAuthenticationFailureHandler defaultFailureHandler = new SimpleUrlAuthenticationFailureHandler( - "/login?error"); - - private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) throws IOException, ServletException { - if (exception instanceof CompromisedPasswordException) { - this.redirectStrategy.sendRedirect(request, response, "/reset-password"); - return; - } - this.defaultFailureHandler.onAuthenticationFailure(request, response, exception); - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@Bean -open fun filterChain(http:HttpSecurity): SecurityFilterChain { - http { - authorizeHttpRequests { - authorize(anyRequest, authenticated) - } - formLogin { - failureHandler = CompromisedPasswordAuthenticationFailureHandler() - } - } - return http.build() -} - -@Bean -open fun compromisedPasswordChecker(): CompromisedPasswordChecker { - return HaveIBeenPwnedRestApiPasswordChecker() -} - -class CompromisedPasswordAuthenticationFailureHandler : AuthenticationFailureHandler { - private val defaultFailureHandler = SimpleUrlAuthenticationFailureHandler("/login?error") - private val redirectStrategy = DefaultRedirectStrategy() - - override fun onAuthenticationFailure( - request: HttpServletRequest, - response: HttpServletResponse, - exception: AuthenticationException - ) { - if (exception is CompromisedPasswordException) { - redirectStrategy.sendRedirect(request, response, "/reset-password") - return - } - defaultFailureHandler.onAuthenticationFailure(request, response, exception) - } -} ----- -====== +include-code::./CompromisedPasswordCheckerUsage[tag=configuration,indent=0] diff --git a/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationcompromisedpasswordcheck/CompromisedPasswordCheckerUsage.java b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationcompromisedpasswordcheck/CompromisedPasswordCheckerUsage.java new file mode 100644 index 00000000000..6c93dcaca63 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationcompromisedpasswordcheck/CompromisedPasswordCheckerUsage.java @@ -0,0 +1,57 @@ +package org.springframework.security.docs.features.authentication.authenticationcompromisedpasswordcheck; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.password.CompromisedPasswordChecker; +import org.springframework.security.authentication.password.CompromisedPasswordException; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.password.HaveIBeenPwnedRestApiPasswordChecker; + +public class CompromisedPasswordCheckerUsage { + // tag::configuration[] + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(authorize -> authorize + .anyRequest().authenticated() + ) + .formLogin((login) -> login + .failureHandler(new CompromisedPasswordAuthenticationFailureHandler()) + ); + return http.build(); + } + + @Bean + public CompromisedPasswordChecker compromisedPasswordChecker() { + return new HaveIBeenPwnedRestApiPasswordChecker(); + } + + static class CompromisedPasswordAuthenticationFailureHandler implements AuthenticationFailureHandler { + + private final SimpleUrlAuthenticationFailureHandler defaultFailureHandler = new SimpleUrlAuthenticationFailureHandler( + "/login?error"); + + private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + if (exception instanceof CompromisedPasswordException) { + this.redirectStrategy.sendRedirect(request, response, "/reset-password"); + return; + } + this.defaultFailureHandler.onAuthenticationFailure(request, response, exception); + } + + } + // end::configuration[] +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstorageargon2/Argon2PasswordEncoderUsage.java b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstorageargon2/Argon2PasswordEncoderUsage.java new file mode 100644 index 00000000000..c8fcae97719 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstorageargon2/Argon2PasswordEncoderUsage.java @@ -0,0 +1,16 @@ +package org.springframework.security.docs.features.authentication.authenticationpasswordstorageargon2; + +import static org.junit.Assert.assertTrue; + +import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; + +public class Argon2PasswordEncoderUsage { + public void testArgon2PasswordEncoder() { + // tag::argon2PasswordEncoder[] + // Create an encoder with all the defaults + Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8(); + String result = encoder.encode("myPassword"); + assertTrue(encoder.matches("myPassword", result)); + // end::argon2PasswordEncoder[] + } +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragebcrypt/BCryptPasswordEncoderUsage.java b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragebcrypt/BCryptPasswordEncoderUsage.java new file mode 100644 index 00000000000..c67e087e2b3 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragebcrypt/BCryptPasswordEncoderUsage.java @@ -0,0 +1,16 @@ +package org.springframework.security.docs.features.authentication.authenticationpasswordstoragebcrypt; + +import static org.junit.Assert.assertTrue; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +public class BCryptPasswordEncoderUsage { + public void testBCryptPasswordEncoder() { + // tag::bcryptPasswordEncoder[] + // Create an encoder with strength 16 + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16); + String result = encoder.encode("myPassword"); + assertTrue(encoder.matches("myPassword", result)); + // end::bcryptPasswordEncoder[] + } +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragedepgettingstarted/WithDefaultPasswordEncoderUsage.java b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragedepgettingstarted/WithDefaultPasswordEncoderUsage.java new file mode 100644 index 00000000000..941c49a94a9 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragedepgettingstarted/WithDefaultPasswordEncoderUsage.java @@ -0,0 +1,38 @@ +package org.springframework.security.docs.features.authentication.authenticationpasswordstoragedepgettingstarted; + +import java.util.List; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import static org.springframework.security.core.userdetails.User.UserBuilder; + +public class WithDefaultPasswordEncoderUsage { + public UserDetails createSingleUser() { + // tag::createSingleUser[] + UserDetails user = User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .roles("user") + .build(); + System.out.println(user.getPassword()); + // {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG + // end::createSingleUser[] + return user; + } + + public List createMultipleUsers() { + // tag::createMultipleUsers[] + 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(); + // end::createMultipleUsers[] + return List.of(user, admin); + } +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragedpe/DelegatingPasswordEncoderUsage.java b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragedpe/DelegatingPasswordEncoderUsage.java new file mode 100644 index 00000000000..fb50c5dd322 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragedpe/DelegatingPasswordEncoderUsage.java @@ -0,0 +1,43 @@ +package org.springframework.security.docs.features.authentication.authenticationpasswordstoragedpe; + +import java.util.HashMap; +import java.util.Map; +import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.DelegatingPasswordEncoder; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; +import org.springframework.security.crypto.password.StandardPasswordEncoder; +import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; + +public class DelegatingPasswordEncoderUsage { + PasswordEncoder defaultDelegatingPasswordEncoder() { + // tag::createDefaultPasswordEncoder[] + PasswordEncoder passwordEncoder = + PasswordEncoderFactories.createDelegatingPasswordEncoder(); + // end::createDefaultPasswordEncoder[] + return passwordEncoder; + } + + PasswordEncoder customDelegatingPasswordEncoder() { + // tag::createCustomPasswordEncoder[] + 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); + // end::createCustomPasswordEncoder[] + return passwordEncoder; + } +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragepbkdf2/Pbkdf2PasswordEncoderUsage.java b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragepbkdf2/Pbkdf2PasswordEncoderUsage.java new file mode 100644 index 00000000000..cab5e72bac9 --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragepbkdf2/Pbkdf2PasswordEncoderUsage.java @@ -0,0 +1,16 @@ +package org.springframework.security.docs.features.authentication.authenticationpasswordstoragepbkdf2; + +import static org.junit.Assert.assertTrue; + +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; + +public class Pbkdf2PasswordEncoderUsage { + void testPbkdf2PasswordEncoder() { + // tag::pbkdf2PasswordEncoder[] + // Create an encoder with all the defaults + Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8(); + String result = encoder.encode("myPassword"); + assertTrue(encoder.matches("myPassword", result)); + // end::pbkdf2PasswordEncoder[] + } +} diff --git a/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragescrypt/SCryptPasswordEncoderUsage.java b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragescrypt/SCryptPasswordEncoderUsage.java new file mode 100644 index 00000000000..84465918b6d --- /dev/null +++ b/docs/src/test/java/org/springframework/security/docs/features/authentication/authenticationpasswordstoragescrypt/SCryptPasswordEncoderUsage.java @@ -0,0 +1,16 @@ +package org.springframework.security.docs.features.authentication.authenticationpasswordstoragescrypt; + +import static org.junit.Assert.assertTrue; + +import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; + +public class SCryptPasswordEncoderUsage { + void testSCryptPasswordEncoder() { + // tag::sCryptPasswordEncoder[] + // Create an encoder with all the defaults + SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8(); + String result = encoder.encode("myPassword"); + assertTrue(encoder.matches("myPassword", result)); + // end::sCryptPasswordEncoder[] + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationcompromisedpasswordcheck/CompromisedPasswordCheckerUsage.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationcompromisedpasswordcheck/CompromisedPasswordCheckerUsage.kt new file mode 100644 index 00000000000..1add9a46a55 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationcompromisedpasswordcheck/CompromisedPasswordCheckerUsage.kt @@ -0,0 +1,55 @@ +package org.springframework.security.kt.docs.features.authentication.authenticationcompromisedpasswordcheck + +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.context.annotation.Bean +import org.springframework.security.authentication.password.CompromisedPasswordChecker +import org.springframework.security.authentication.password.CompromisedPasswordException +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.invoke +import org.springframework.security.core.AuthenticationException +import org.springframework.security.web.DefaultRedirectStrategy +import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.AuthenticationFailureHandler +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler +import org.springframework.security.web.authentication.password.HaveIBeenPwnedRestApiPasswordChecker + + +class CompromisedPasswordCheckerUsage { + // tag::configuration[] + @Bean + open fun filterChain(http: HttpSecurity): SecurityFilterChain { + http { + authorizeHttpRequests { + authorize(anyRequest, authenticated) + } + formLogin { + authenticationFailureHandler = CompromisedPasswordAuthenticationFailureHandler() + } + } + return http.build() + } + + @Bean + open fun compromisedPasswordChecker(): CompromisedPasswordChecker { + return HaveIBeenPwnedRestApiPasswordChecker() + } + + class CompromisedPasswordAuthenticationFailureHandler : AuthenticationFailureHandler { + private val defaultFailureHandler = SimpleUrlAuthenticationFailureHandler("/login?error") + private val redirectStrategy = DefaultRedirectStrategy() + + override fun onAuthenticationFailure( + request: HttpServletRequest, + response: HttpServletResponse, + exception: AuthenticationException + ) { + if (exception is CompromisedPasswordException) { + redirectStrategy.sendRedirect(request, response, "/reset-password") + return + } + defaultFailureHandler.onAuthenticationFailure(request, response, exception) + } + } + // end::configuration[] +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstorageargon2/Argon2PasswordEncoderUsage.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstorageargon2/Argon2PasswordEncoderUsage.kt new file mode 100644 index 00000000000..3b46cffd603 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstorageargon2/Argon2PasswordEncoderUsage.kt @@ -0,0 +1,15 @@ +package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstorageargon2 + +import org.junit.Assert.assertTrue +import org.springframework.security.crypto.argon2.Argon2PasswordEncoder + +class Argon2PasswordEncoderUsage { + fun testArgon2PasswordEncoder() { + // tag::argon2PasswordEncoder[] + // Create an encoder with all the defaults + val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8() + val result: String = encoder.encode("myPassword") + assertTrue(encoder.matches("myPassword", result)) + // end::argon2PasswordEncoder[] + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragebcrypt/BCryptPasswordEncoderUsage.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragebcrypt/BCryptPasswordEncoderUsage.kt new file mode 100644 index 00000000000..09a22f8c094 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragebcrypt/BCryptPasswordEncoderUsage.kt @@ -0,0 +1,15 @@ +package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstoragebcrypt + +import org.junit.Assert.assertTrue +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder + +class BCryptPasswordEncoderUsage { + fun testBCryptPasswordEncoder() { + // tag::bcryptPasswordEncoder[] + // Create an encoder with strength 16 + val encoder = BCryptPasswordEncoder(16) + val result: String = encoder.encode("myPassword") + assertTrue(encoder.matches("myPassword", result)) + // end::bcryptPasswordEncoder[] + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragedepgettingstarted/WithDefaultPasswordEncoderUsage.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragedepgettingstarted/WithDefaultPasswordEncoderUsage.kt new file mode 100644 index 00000000000..6b973b286bd --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragedepgettingstarted/WithDefaultPasswordEncoderUsage.kt @@ -0,0 +1,36 @@ +package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstoragedepgettingstarted + +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetails + +class WithDefaultPasswordEncoderUsage { + fun createSingleUser(): UserDetails { + // tag::createSingleUser[] + val user = User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .roles("user") + .build() + println(user.password) + // {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG + // end::createSingleUser[] + return user + } + + fun createMultipleUsers(): List { + // tag::createMultipleUsers[] + 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() + // end::createMultipleUsers[] + return listOf(user, admin) + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragedpe/DelegatingPasswordEncoderUsage.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragedpe/DelegatingPasswordEncoderUsage.kt new file mode 100644 index 00000000000..63ac610e641 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragedpe/DelegatingPasswordEncoderUsage.kt @@ -0,0 +1,39 @@ +package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstoragedpe + +import org.springframework.security.crypto.argon2.Argon2PasswordEncoder +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.factory.PasswordEncoderFactories +import org.springframework.security.crypto.password.DelegatingPasswordEncoder +import org.springframework.security.crypto.password.NoOpPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder +import org.springframework.security.crypto.password.StandardPasswordEncoder +import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder + +class DelegatingPasswordEncoderUsage { + fun defaultDelegatingPasswordEncoder(): PasswordEncoder { + // tag::createDefaultPasswordEncoder[] + val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder() + // end::createDefaultPasswordEncoder[] + return passwordEncoder + } + + fun customDelegatingPasswordEncoder(): PasswordEncoder { + // tag::createCustomPasswordEncoder[] + val idForEncode = "bcrypt" + val encoders: MutableMap = 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) + // end::createCustomPasswordEncoder[] + return passwordEncoder + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragepbkdf2/Pbkdf2PasswordEncoderUsage.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragepbkdf2/Pbkdf2PasswordEncoderUsage.kt new file mode 100644 index 00000000000..0b541ed9edf --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragepbkdf2/Pbkdf2PasswordEncoderUsage.kt @@ -0,0 +1,15 @@ +package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstoragepbkdf2 + +import org.junit.Assert.assertTrue +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder + +class Pbkdf2PasswordEncoderUsage { + fun testPbkdf2PasswordEncoder() { + // tag::pbkdf2PasswordEncoder[] + // Create an encoder with all the defaults + val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8() + val result: String = encoder.encode("myPassword") + assertTrue(encoder.matches("myPassword", result)) + // end::pbkdf2PasswordEncoder[] + } +} diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragescrypt/SCryptPasswordEncoderUsage.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragescrypt/SCryptPasswordEncoderUsage.kt new file mode 100644 index 00000000000..d52478d8062 --- /dev/null +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/features/authentication/authenticationpasswordstoragescrypt/SCryptPasswordEncoderUsage.kt @@ -0,0 +1,15 @@ +package org.springframework.security.kt.docs.features.authentication.authenticationpasswordstoragescrypt + +import org.junit.Assert.assertTrue +import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder + +class SCryptPasswordEncoderUsage { + fun testSCryptPasswordEncoder() { + // tag::sCryptPasswordEncoder[] + // Create an encoder with all the defaults + val encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8() + val result: String = encoder.encode("myPassword") + assertTrue(encoder.matches("myPassword", result)) + // end::sCryptPasswordEncoder[] + } +}