diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 3c0f3a0679e..fb7a21a032a 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -3701,7 +3701,6 @@ public HttpSecurity securityMatcher(String... patterns) { * http * // ... * .webAuthn((webAuthn) -> webAuthn - * .rpName("Spring Security Relying Party") * .rpId("example.com") * .allowedOrigins("https://example.com") * ); diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java index 1423c754659..6f8e430f3e9 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java @@ -192,9 +192,9 @@ private WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations( if (webauthnOperationsBean.isPresent()) { return webauthnOperationsBean.get(); } - Webauthn4JRelyingPartyOperations result = new Webauthn4JRelyingPartyOperations(userEntities, userCredentials, - PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(), this.allowedOrigins); - return result; + String rpName = (this.rpName != null) ? this.rpName : this.rpId; + return new Webauthn4JRelyingPartyOperations(userEntities, userCredentials, + PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins); } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java index 88649f7d703..244a00f1211 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java @@ -18,12 +18,15 @@ 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; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -38,7 +41,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; +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; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -111,6 +117,42 @@ public void webauthnWhenFormLoginAndDefaultRegistrationPageConfiguredThenNoDupli .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(); @@ -137,7 +179,27 @@ UserDetailsService userDetailsService() { @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(); } } diff --git a/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc b/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc index 9b0cd52356d..f115b9543a3 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc @@ -64,7 +64,6 @@ SecurityFilterChain filterChain(HttpSecurity http) { // ... .formLogin(withDefaults()) .webAuthn((webAuthn) -> webAuthn - .rpName("Spring Security Relying Party") .rpId("example.com") .allowedOrigins("https://example.com") ); @@ -91,7 +90,6 @@ Kotlin:: open fun filterChain(http: HttpSecurity): SecurityFilterChain { http { webAuthn { - rpName = "Spring Security Relying Party" rpId = "example.com" allowedOrigins = setOf("https://example.com") }