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 ea44de22bf..2e0b6534d1 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 @@ -3758,7 +3758,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder> PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) { Optional 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)); } } 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 db96afd6ec..6dffd8b3e8 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 @@ -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(); } } diff --git a/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc b/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc index 0ccbf18cc1..06b81941af 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc @@ -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