2
0
Эх сурвалжийг харах

Default WebAuthnConfigurer#rpName to rpId

In WebAuthn L3 spec, PublicKeyCredentialEntity.name is deprecated:

> This member is deprecated because many clients do not display it,
> but it remains a required dictionary member for backwards compatibility.
> Relying Parties MAY, as a safe default, set this equal to the RP ID.

Source: https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialentity

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Daniel Garnier-Moiroux 3 долоо хоног өмнө
parent
commit
fed6df5167

+ 0 - 1
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

@@ -3701,7 +3701,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
 	 * 		http
 	 * 			// ...
 	 * 			.webAuthn((webAuthn) -&gt; webAuthn
-	 * 				.rpName("Spring Security Relying Party")
 	 * 				.rpId("example.com")
 	 * 				.allowedOrigins("https://example.com")
 	 * 			);

+ 3 - 3
config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java

@@ -192,9 +192,9 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
 		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);
 	}
 
 }

+ 63 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java

@@ -18,12 +18,15 @@ package org.springframework.security.config.annotation.web.configurers;
 
 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 org.springframework.test.web.servlet.MockMvc;
 
 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 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();
@@ -137,7 +179,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();
 		}
 
 	}

+ 0 - 2
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")
 		}