Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5a379cc91 | |||
| 7ad28772bc | |||
| 2678925c21 | |||
| 87472c9ab4 | |||
| ea036702fc | |||
| 09805317e6 | |||
| 4ab933803f | |||
| d2b1cb572f | |||
| 70076188af | |||
| 57c9b1365c | |||
| 1c3d28f14d | |||
| aaadb43ef8 | |||
| a6c1a02afa | |||
| baebd04df7 | |||
| d0166004aa | |||
| 9f96fbcda0 | |||
| ccffb48fd1 | |||
| d0fcdebe88 | |||
| af47cc2abe | |||
| f997e22d9d | |||
| c85cb2d1ef | |||
| bf26dd9b33 | |||
| ff908c4d7c | |||
| 521f533fc4 | |||
| f988272fff | |||
| 532d0bef14 | |||
| c1e9e10bf0 | |||
| fed6df5167 | |||
| 8fa2fc0e1e | |||
| 4feeb0f843 | |||
| 6501e97ece | |||
| ee49c18ce2 | |||
| f0afca7610 | |||
| 8b0689cbb8 | |||
| 28e158d1cb | |||
| f548aaf5c5 | |||
| 1c112005fa | |||
| e0a71eb00e | |||
| 42ddaba870 | |||
| dcb4e47cd5 | |||
| 82f87cf2b6 | |||
| dc5aed9b5f |
-1
@@ -3758,7 +3758,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
||||
* http
|
||||
* // ...
|
||||
* .webAuthn((webAuthn) -> webAuthn
|
||||
* .rpName("Spring Security Relying Party")
|
||||
* .rpId("example.com")
|
||||
* .allowedOrigins("https://example.com")
|
||||
* );
|
||||
|
||||
+4
-3
@@ -243,9 +243,10 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
|
||||
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
|
||||
WebAuthnRelyingPartyOperations.class);
|
||||
return webauthnOperationsBean.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities,
|
||||
userCredentials, PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(),
|
||||
this.allowedOrigins));
|
||||
String rpName = (this.rpName != null) ? this.rpName : this.rpId;
|
||||
return webauthnOperationsBean
|
||||
.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities, userCredentials,
|
||||
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+61
-1
@@ -19,6 +19,8 @@ package org.springframework.security.config.annotation.web.configurers;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@@ -52,6 +54,8 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
@@ -127,6 +131,42 @@ public class WebAuthnConfigurerTests {
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void webauthnWhenConfiguredDefaultsRpNameToRpId() throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
|
||||
String response = this.mvc
|
||||
.perform(post("/webauthn/register/options").with(csrf())
|
||||
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
JsonNode parsedResponse = mapper.readTree(response);
|
||||
|
||||
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
|
||||
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void webauthnWhenRpNameConfiguredUsesRpName() throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
this.spring.register(CustomRpNameWebauthnConfiguration.class).autowire();
|
||||
String response = this.mvc
|
||||
.perform(post("/webauthn/register/options").with(csrf())
|
||||
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
JsonNode parsedResponse = mapper.readTree(response);
|
||||
|
||||
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
|
||||
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("Test RP Name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
|
||||
this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire();
|
||||
@@ -300,7 +340,27 @@ public class WebAuthnConfigurerTests {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.formLogin(Customizer.withDefaults()).webAuthn(Customizer.withDefaults()).build();
|
||||
return http.formLogin(Customizer.withDefaults())
|
||||
.webAuthn((webauthn) -> webauthn.rpId("example.com"))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class CustomRpNameWebauthnConfiguration {
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.formLogin(Customizer.withDefaults())
|
||||
.webAuthn((webauthn) -> webauthn.rpId("example.com").rpName("Test RP Name"))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+21
@@ -16,12 +16,14 @@
|
||||
|
||||
package org.springframework.security.crypto.bcrypt;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
@@ -253,4 +255,23 @@ public class BCryptPasswordEncoderTests {
|
||||
assertThat(encoder.matches(password73chars, encodedPassword73chars)).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes gh-18133
|
||||
* @author StringManolo
|
||||
*/
|
||||
@Test
|
||||
void passwordLargerThan72BytesShouldThrowIllegalArgumentException() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
String singleByteChars = "a".repeat(68);
|
||||
String password72Bytes = singleByteChars + "😀";
|
||||
assertThat(password72Bytes.length()).isEqualTo(70);
|
||||
assertThat(password72Bytes.getBytes(StandardCharsets.UTF_8).length).isEqualTo(72);
|
||||
assertThatNoException().isThrownBy(() -> encoder.encode(password72Bytes));
|
||||
String singleByteCharsTooLong = "a".repeat(69);
|
||||
String password73Bytes = singleByteCharsTooLong + "😀";
|
||||
assertThat(password73Bytes.getBytes(StandardCharsets.UTF_8).length).isEqualTo(73);
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> encoder.encode(password73Bytes))
|
||||
.withMessageContaining("password cannot be more than 72 bytes");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<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)
|
||||
----
|
||||
======
|
||||
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]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
[[webflux-cors]]
|
||||
= CORS
|
||||
|
||||
@@ -75,3 +74,11 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
CORS is a browser-based security feature.
|
||||
By disabling CORS in Spring Security, you are not removing CORS protection from your browser.
|
||||
Instead, you are removing CORS support from Spring Security, and users will not be able to interact with your Spring backend from a cross-origin browser application.
|
||||
To fix CORS errors in your application, you must enable CORS support, and provide an appropriate configuration source.
|
||||
====
|
||||
|
||||
@@ -65,7 +65,6 @@ SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
// ...
|
||||
.formLogin(withDefaults())
|
||||
.webAuthn((webAuthn) -> webAuthn
|
||||
.rpName("Spring Security Relying Party")
|
||||
.rpId("example.com")
|
||||
.allowedOrigins("https://example.com")
|
||||
// optional properties
|
||||
@@ -96,7 +95,6 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
// ...
|
||||
http {
|
||||
webAuthn {
|
||||
rpName = "Spring Security Relying Party"
|
||||
rpId = "example.com"
|
||||
allowedOrigins = setOf("https://example.com")
|
||||
// optional properties
|
||||
|
||||
@@ -183,3 +183,11 @@ fun corsConfigurationSource(): UrlBasedCorsConfigurationSource {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
CORS is a browser-based security feature.
|
||||
By disabling CORS in Spring Security with `.cors(CorsConfigurer::disable)`, you are not removing CORS protection from your browser.
|
||||
Instead, you are removing CORS support from Spring Security, and users will not be able to interact with your Spring backend from a cross-origin browser application.
|
||||
To fix CORS errors in your application, you must enable CORS support, and provide an appropriate configuration source.
|
||||
====
|
||||
|
||||
+57
@@ -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[]
|
||||
}
|
||||
+16
@@ -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[]
|
||||
}
|
||||
}
|
||||
+16
@@ -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[]
|
||||
}
|
||||
}
|
||||
+38
@@ -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<UserDetails> 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);
|
||||
}
|
||||
}
|
||||
+43
@@ -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;
|
||||
}
|
||||
}
|
||||
+16
@@ -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[]
|
||||
}
|
||||
}
|
||||
+16
@@ -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[]
|
||||
}
|
||||
}
|
||||
+55
@@ -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[]
|
||||
}
|
||||
+15
@@ -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[]
|
||||
}
|
||||
}
|
||||
+15
@@ -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[]
|
||||
}
|
||||
}
|
||||
+36
@@ -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<UserDetails> {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
+39
@@ -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<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)
|
||||
// end::createCustomPasswordEncoder[]
|
||||
return passwordEncoder
|
||||
}
|
||||
}
|
||||
+15
@@ -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[]
|
||||
}
|
||||
}
|
||||
+15
@@ -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[]
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
springBootVersion=3.3.3
|
||||
version=6.5.6
|
||||
version=6.5.7
|
||||
samplesBranch=6.5.x
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -6,7 +6,7 @@ io-spring-nohttp = "0.0.11"
|
||||
jakarta-websocket = "2.2.0"
|
||||
org-apache-directory-server = "1.5.5"
|
||||
org-apache-maven-resolver = "1.9.24"
|
||||
org-aspectj = "1.9.24"
|
||||
org-aspectj = "1.9.25"
|
||||
org-bouncycastle = "1.80"
|
||||
org-eclipse-jetty = "11.0.26"
|
||||
org-jetbrains-kotlin = "1.9.25"
|
||||
@@ -14,11 +14,11 @@ org-jetbrains-kotlinx = "1.10.2"
|
||||
org-mockito = "5.17.0"
|
||||
org-opensaml = "4.3.2"
|
||||
org-opensaml5 = "5.1.2"
|
||||
org-springframework = "6.2.12"
|
||||
org-springframework = "6.2.13"
|
||||
|
||||
[libraries]
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.20"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.4.1"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.5"
|
||||
com-google-inject-guice = "com.google.inject:guice:3.0"
|
||||
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
|
||||
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.37.4"
|
||||
@@ -29,15 +29,15 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11"
|
||||
com-unboundid-unboundid-ldapsdk7 = "com.unboundid:unboundid-ldapsdk:7.0.1"
|
||||
commons-collections = "commons-collections:commons-collections:3.2.2"
|
||||
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.12"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.13"
|
||||
io-mockk = "io.mockk:mockk:1.14.6"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.19"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2024.0.12"
|
||||
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
|
||||
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
|
||||
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
|
||||
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
|
||||
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
|
||||
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.11"
|
||||
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.13"
|
||||
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:2.1.1"
|
||||
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
|
||||
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.1.0"
|
||||
@@ -71,7 +71,7 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on",
|
||||
org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" }
|
||||
org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" }
|
||||
org-hamcrest = "org.hamcrest:hamcrest:2.2"
|
||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.33.Final"
|
||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.34.Final"
|
||||
org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
|
||||
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
|
||||
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25"
|
||||
@@ -89,7 +89,7 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup
|
||||
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
|
||||
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
|
||||
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
|
||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.11"
|
||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.12"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.15"
|
||||
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
|
||||
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
|
||||
|
||||
+4
-2
@@ -72,8 +72,10 @@ public final class JwtTypeValidator implements OAuth2TokenValidator<Jwt> {
|
||||
if (this.allowEmpty && !StringUtils.hasText(typ)) {
|
||||
return OAuth2TokenValidatorResult.success();
|
||||
}
|
||||
if (this.validTypes.contains(typ)) {
|
||||
return OAuth2TokenValidatorResult.success();
|
||||
for (String validType : this.validTypes) {
|
||||
if (validType.equalsIgnoreCase(typ)) {
|
||||
return OAuth2TokenValidatorResult.success();
|
||||
}
|
||||
}
|
||||
return OAuth2TokenValidatorResult.failure(new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN,
|
||||
"the given typ value needs to be one of " + this.validTypes,
|
||||
|
||||
+8
@@ -44,4 +44,12 @@ class JwtTypeValidatorTests {
|
||||
assertThat(validator.validate(jwt.build()).hasErrors()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWhenTypHeaderHasDifferentCaseThenSuccess() {
|
||||
Jwt.Builder jwt = TestJwts.jwt();
|
||||
JwtTypeValidator validator = new JwtTypeValidator("at+jwt");
|
||||
jwt.header(JoseHeaderNames.TYP, "AT+JWT");
|
||||
assertThat(validator.validate(jwt.build()).hasErrors()).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-7
@@ -30,7 +30,6 @@ import org.springframework.security.authentication.ott.OneTimeTokenService;
|
||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
@@ -68,17 +67,12 @@ public final class GenerateOneTimeTokenFilter extends OncePerRequestFilter {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
String username = request.getParameter("username");
|
||||
if (!StringUtils.hasText(username)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
GenerateOneTimeTokenRequest generateRequest = this.requestResolver.resolve(request);
|
||||
OneTimeToken ott = this.tokenService.generate(generateRequest);
|
||||
if (generateRequest == null) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
OneTimeToken ott = this.tokenService.generate(generateRequest);
|
||||
this.tokenGenerationSuccessHandler.handle(request, response, ott);
|
||||
}
|
||||
|
||||
|
||||
+18
@@ -113,4 +113,22 @@ public class GenerateOneTimeTokenFilterTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void filterWhenUsernameFormParamIsEmptyButRequestResolverCanResolveThenSuccess()
|
||||
throws ServletException, IOException {
|
||||
GenerateOneTimeTokenRequestResolver requestResolver = mock();
|
||||
given(this.oneTimeTokenService.generate(ArgumentMatchers.any(GenerateOneTimeTokenRequest.class)))
|
||||
.willReturn((new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now())));
|
||||
given(requestResolver.resolve(this.request)).willReturn(new GenerateOneTimeTokenRequest(USERNAME));
|
||||
|
||||
GenerateOneTimeTokenFilter filter = new GenerateOneTimeTokenFilter(this.oneTimeTokenService,
|
||||
this.successHandler);
|
||||
filter.setRequestResolver(requestResolver);
|
||||
|
||||
filter.doFilter(this.request, this.response, this.filterChain);
|
||||
|
||||
verify(this.oneTimeTokenService).generate(ArgumentMatchers.any(GenerateOneTimeTokenRequest.class));
|
||||
assertThat(this.response.getRedirectedUrl()).isEqualTo("/login/ott");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user