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 cabfba23ec..6ef44031c5 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 @@ -2052,7 +2052,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 60674ab4ba..80b301e96c 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; @@ -54,6 +56,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; @@ -137,6 +141,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(); @@ -334,8 +374,7 @@ public class WebAuthnConfigurerTests { http .formLogin(Customizer.withDefaults()) .webAuthn((authn) -> authn - .rpId("spring.io") - .rpName("spring") + .rpId("example.com") ); // @formatter:on return http.build(); @@ -343,6 +382,24 @@ public class WebAuthnConfigurerTests { } + @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(); + } + + } + @Configuration @EnableWebSecurity static class NoFormLoginAndDefaultRegistrationPageConfiguration { 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