@@ -67,68 +67,12 @@ Instead Spring Security introduces `DelegatingPasswordEncoder`, which solves all
6767You can easily construct an instance of `DelegatingPasswordEncoder` by using `PasswordEncoderFactories`:
6868
6969.Create Default DelegatingPasswordEncoder
70- [tabs]
71- ======
72- Java::
73- +
74- [source,java,role="primary"]
75- ----
76- PasswordEncoder passwordEncoder =
77- PasswordEncoderFactories.createDelegatingPasswordEncoder();
78- ----
79-
80- Kotlin::
81- +
82- [source,kotlin,role="secondary"]
83- ----
84- val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
85- ----
86- ======
70+ include-code::./DelegatingPasswordEncoderUsage[tag=createDefaultPasswordEncoder,indent=0]
8771
8872Alternatively, you can create your own custom instance:
8973
9074.Create Custom DelegatingPasswordEncoder
91- [tabs]
92- ======
93- Java::
94- +
95- [source,java,role="primary"]
96- ----
97- String idForEncode = "bcrypt";
98- Map encoders = new HashMap<>();
99- encoders.put(idForEncode, new BCryptPasswordEncoder());
100- encoders.put("noop", NoOpPasswordEncoder.getInstance());
101- encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
102- encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
103- encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
104- encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
105- encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
106- encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
107- encoders.put("sha256", new StandardPasswordEncoder());
108-
109- PasswordEncoder passwordEncoder =
110- new DelegatingPasswordEncoder(idForEncode, encoders);
111- ----
112-
113- Kotlin::
114- +
115- [source,kotlin,role="secondary"]
116- ----
117- val idForEncode = "bcrypt"
118- val encoders: MutableMap<String, PasswordEncoder> = mutableMapOf()
119- encoders[idForEncode] = BCryptPasswordEncoder()
120- encoders["noop"] = NoOpPasswordEncoder.getInstance()
121- encoders["pbkdf2"] = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5()
122- encoders["pbkdf2@SpringSecurity_v5_8"] = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
123- encoders["scrypt"] = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1()
124- encoders["scrypt@SpringSecurity_v5_8"] = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
125- encoders["argon2"] = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2()
126- encoders["argon2@SpringSecurity_v5_8"] = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
127- encoders["sha256"] = StandardPasswordEncoder()
128-
129- val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, encoders)
130- ----
131- ======
75+ include-code::./DelegatingPasswordEncoderUsage[tag=createCustomPasswordEncoder,indent=0]
13276
13377[[authentication-password-storage-dpe-format]]
13478=== 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
209153There are convenience mechanisms to make this easier, but this is still not intended for production.
210154
211155.withDefaultPasswordEncoder Example
212- [tabs]
213- ======
214- Java::
215- +
216- [source,java,role="primary",attrs="-attributes"]
217- ----
218- UserDetails user = User.withDefaultPasswordEncoder()
219- .username("user")
220- .password("password")
221- .roles("user")
222- .build();
223- System.out.println(user.getPassword());
224- // {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
225- ----
226-
227- Kotlin::
228- +
229- [source,kotlin,role="secondary",attrs="-attributes"]
230- ----
231- val user = User.withDefaultPasswordEncoder()
232- .username("user")
233- .password("password")
234- .roles("user")
235- .build()
236- println(user.password)
237- // {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
238- ----
239- ======
156+ include-code::./WithDefaultPasswordEncoderUsage[tag=createSingleUser,indent=0]
240157
241158If you are creating multiple users, you can also reuse the builder:
242159
243160.withDefaultPasswordEncoder Reusing the Builder
244- [tabs]
245- ======
246- Java::
247- +
248- [source,java,role="primary"]
249- ----
250- UserBuilder users = User.withDefaultPasswordEncoder();
251- UserDetails user = users
252- .username("user")
253- .password("password")
254- .roles("USER")
255- .build();
256- UserDetails admin = users
257- .username("admin")
258- .password("password")
259- .roles("USER","ADMIN")
260- .build();
261- ----
262-
263- Kotlin::
264- +
265- [source,kotlin,role="secondary"]
266- ----
267- val users = User.withDefaultPasswordEncoder()
268- val user = users
269- .username("user")
270- .password("password")
271- .roles("USER")
272- .build()
273- val admin = users
274- .username("admin")
275- .password("password")
276- .roles("USER", "ADMIN")
277- .build()
278- ----
279- ======
161+ include-code::./WithDefaultPasswordEncoderUsage[tag=createMultipleUsers,indent=0]
280162
281163This does hash the password that is stored, but the passwords are still exposed in memory and in the compiled source code.
282164Therefore, it is still not considered secure for a production environment.
@@ -337,28 +219,7 @@ The default implementation of `BCryptPasswordEncoder` uses strength 10 as mentio
337219tune and test the strength parameter on your own system so that it takes roughly 1 second to verify a password.
338220
339221.BCryptPasswordEncoder
340- [tabs]
341- ======
342- Java::
343- +
344- [source,java,role="primary"]
345- ----
346- // Create an encoder with strength 16
347- BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
348- String result = encoder.encode("myPassword");
349- assertTrue(encoder.matches("myPassword", result));
350- ----
351-
352- Kotlin::
353- +
354- [source,kotlin,role="secondary"]
355- ----
356- // Create an encoder with strength 16
357- val encoder = BCryptPasswordEncoder(16)
358- val result: String = encoder.encode("myPassword")
359- assertTrue(encoder.matches("myPassword", result))
360- ----
361- ======
222+ include-code::./BCryptPasswordEncoderUsage[tag=bcryptPasswordEncoder,indent=0]
362223
363224[[authentication-password-storage-argon2]]
364225== Argon2PasswordEncoder
@@ -370,28 +231,7 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second
370231The current implementation of the `Argon2PasswordEncoder` requires BouncyCastle.
371232
372233.Argon2PasswordEncoder
373- [tabs]
374- ======
375- Java::
376- +
377- [source,java,role="primary"]
378- ----
379- // Create an encoder with all the defaults
380- Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
381- String result = encoder.encode("myPassword");
382- assertTrue(encoder.matches("myPassword", result));
383- ----
384-
385- Kotlin::
386- +
387- [source,kotlin,role="secondary"]
388- ----
389- // Create an encoder with all the defaults
390- val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
391- val result: String = encoder.encode("myPassword")
392- assertTrue(encoder.matches("myPassword", result))
393- ----
394- ======
234+ include-code::./Argon2PasswordEncoderUsage[tag=argon2PasswordEncoder,indent=0]
395235
396236[[authentication-password-storage-pbkdf2]]
397237== Pbkdf2PasswordEncoder
@@ -402,28 +242,7 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second
402242This algorithm is a good choice when FIPS certification is required.
403243
404244.Pbkdf2PasswordEncoder
405- [tabs]
406- ======
407- Java::
408- +
409- [source,java,role="primary"]
410- ----
411- // Create an encoder with all the defaults
412- Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
413- String result = encoder.encode("myPassword");
414- assertTrue(encoder.matches("myPassword", result));
415- ----
416-
417- Kotlin::
418- +
419- [source,kotlin,role="secondary"]
420- ----
421- // Create an encoder with all the defaults
422- val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
423- val result: String = encoder.encode("myPassword")
424- assertTrue(encoder.matches("myPassword", result))
425- ----
426- ======
245+ include-code::./Pbkdf2PasswordEncoderUsage[tag=pbkdf2PasswordEncoder,indent=0]
427246
428247[[authentication-password-storage-scrypt]]
429248== SCryptPasswordEncoder
@@ -433,28 +252,7 @@ To defeat password cracking on custom hardware, scrypt is a deliberately slow al
433252Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
434253
435254.SCryptPasswordEncoder
436- [tabs]
437- ======
438- Java::
439- +
440- [source,java,role="primary"]
441- ----
442- // Create an encoder with all the defaults
443- SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
444- String result = encoder.encode("myPassword");
445- assertTrue(encoder.matches("myPassword", result));
446- ----
447-
448- Kotlin::
449- +
450- [source,kotlin,role="secondary"]
451- ----
452- // Create an encoder with all the defaults
453- val encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
454- val result: String = encoder.encode("myPassword")
455- assertTrue(encoder.matches("myPassword", result))
456- ----
457- ======
255+ include-code::./SCryptPasswordEncoderUsage[tag=sCryptPasswordEncoder,indent=0]
458256
459257[[authentication-password-storage-other]]
460258== Other ``PasswordEncoder``s
@@ -606,86 +404,4 @@ However, just a 401 or the redirect is not so useful in that case, it will cause
606404In 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:
607405
608406.Using CompromisedPasswordChecker
609- [tabs]
610- ======
611- Java::
612- +
613- [source,java,role="primary"]
614- ----
615- @Bean
616- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
617- http
618- .authorizeHttpRequests(authorize -> authorize
619- .anyRequest().authenticated()
620- )
621- .formLogin((login) -> login
622- .failureHandler(new CompromisedPasswordAuthenticationFailureHandler())
623- );
624- return http.build();
625- }
626-
627- @Bean
628- public CompromisedPasswordChecker compromisedPasswordChecker() {
629- return new HaveIBeenPwnedRestApiPasswordChecker();
630- }
631-
632- static class CompromisedPasswordAuthenticationFailureHandler implements AuthenticationFailureHandler {
633-
634- private final SimpleUrlAuthenticationFailureHandler defaultFailureHandler = new SimpleUrlAuthenticationFailureHandler(
635- "/login?error");
636-
637- private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
638-
639- @Override
640- public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
641- AuthenticationException exception) throws IOException, ServletException {
642- if (exception instanceof CompromisedPasswordException) {
643- this.redirectStrategy.sendRedirect(request, response, "/reset-password");
644- return;
645- }
646- this.defaultFailureHandler.onAuthenticationFailure(request, response, exception);
647- }
648-
649- }
650- ----
651-
652- Kotlin::
653- +
654- [source,kotlin,role="secondary"]
655- ----
656- @Bean
657- open fun filterChain(http:HttpSecurity): SecurityFilterChain {
658- http {
659- authorizeHttpRequests {
660- authorize(anyRequest, authenticated)
661- }
662- formLogin {
663- failureHandler = CompromisedPasswordAuthenticationFailureHandler()
664- }
665- }
666- return http.build()
667- }
668-
669- @Bean
670- open fun compromisedPasswordChecker(): CompromisedPasswordChecker {
671- return HaveIBeenPwnedRestApiPasswordChecker()
672- }
673-
674- class CompromisedPasswordAuthenticationFailureHandler : AuthenticationFailureHandler {
675- private val defaultFailureHandler = SimpleUrlAuthenticationFailureHandler("/login?error")
676- private val redirectStrategy = DefaultRedirectStrategy()
677-
678- override fun onAuthenticationFailure(
679- request: HttpServletRequest,
680- response: HttpServletResponse,
681- exception: AuthenticationException
682- ) {
683- if (exception is CompromisedPasswordException) {
684- redirectStrategy.sendRedirect(request, response, "/reset-password")
685- return
686- }
687- defaultFailureHandler.onAuthenticationFailure(request, response, exception)
688- }
689- }
690- ----
691- ======
407+ include-code::./CompromisedPasswordCheckerUsage[tag=configuration,indent=0]
0 commit comments