Skip to content

Commit fed6df5

Browse files
Kehrlannrwinch
authored andcommitted
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 <[email protected]>
1 parent 4feeb0f commit fed6df5

File tree

4 files changed

+66
-7
lines changed

4 files changed

+66
-7
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3701,7 +3701,6 @@ public HttpSecurity securityMatcher(String... patterns) {
37013701
* http
37023702
* // ...
37033703
* .webAuthn((webAuthn) -&gt; webAuthn
3704-
* .rpName("Spring Security Relying Party")
37053704
* .rpId("example.com")
37063705
* .allowedOrigins("https://example.com")
37073706
* );

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,9 @@ private WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations(
192192
if (webauthnOperationsBean.isPresent()) {
193193
return webauthnOperationsBean.get();
194194
}
195-
Webauthn4JRelyingPartyOperations result = new Webauthn4JRelyingPartyOperations(userEntities, userCredentials,
196-
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(), this.allowedOrigins);
197-
return result;
195+
String rpName = (this.rpName != null) ? this.rpName : this.rpId;
196+
return new Webauthn4JRelyingPartyOperations(userEntities, userCredentials,
197+
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins);
198198
}
199199

200200
}

config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818

1919
import java.util.List;
2020

21+
import com.fasterxml.jackson.databind.JsonNode;
22+
import com.fasterxml.jackson.databind.ObjectMapper;
2123
import org.junit.jupiter.api.Test;
2224
import org.junit.jupiter.api.extension.ExtendWith;
2325

2426
import org.springframework.beans.factory.annotation.Autowired;
2527
import org.springframework.context.annotation.Bean;
2628
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.security.authentication.TestingAuthenticationToken;
2730
import org.springframework.security.config.Customizer;
2831
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2932
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -38,7 +41,10 @@
3841

3942
import static org.assertj.core.api.Assertions.assertThat;
4043
import static org.hamcrest.Matchers.containsString;
44+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
45+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
4146
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
47+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
4248
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
4349
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
4450
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -111,6 +117,42 @@ public void webauthnWhenFormLoginAndDefaultRegistrationPageConfiguredThenNoDupli
111117
.hasSize(1);
112118
}
113119

120+
@Test
121+
void webauthnWhenConfiguredDefaultsRpNameToRpId() throws Exception {
122+
ObjectMapper mapper = new ObjectMapper();
123+
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
124+
String response = this.mvc
125+
.perform(post("/webauthn/register/options").with(csrf())
126+
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
127+
.andExpect(status().is2xxSuccessful())
128+
.andReturn()
129+
.getResponse()
130+
.getContentAsString();
131+
132+
JsonNode parsedResponse = mapper.readTree(response);
133+
134+
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
135+
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("example.com");
136+
}
137+
138+
@Test
139+
void webauthnWhenRpNameConfiguredUsesRpName() throws Exception {
140+
ObjectMapper mapper = new ObjectMapper();
141+
this.spring.register(CustomRpNameWebauthnConfiguration.class).autowire();
142+
String response = this.mvc
143+
.perform(post("/webauthn/register/options").with(csrf())
144+
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
145+
.andExpect(status().is2xxSuccessful())
146+
.andReturn()
147+
.getResponse()
148+
.getContentAsString();
149+
150+
JsonNode parsedResponse = mapper.readTree(response);
151+
152+
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
153+
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("Test RP Name");
154+
}
155+
114156
@Test
115157
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
116158
this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire();
@@ -137,7 +179,27 @@ UserDetailsService userDetailsService() {
137179

138180
@Bean
139181
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
140-
return http.formLogin(Customizer.withDefaults()).webAuthn(Customizer.withDefaults()).build();
182+
return http.formLogin(Customizer.withDefaults())
183+
.webAuthn((webauthn) -> webauthn.rpId("example.com"))
184+
.build();
185+
}
186+
187+
}
188+
189+
@Configuration
190+
@EnableWebSecurity
191+
static class CustomRpNameWebauthnConfiguration {
192+
193+
@Bean
194+
UserDetailsService userDetailsService() {
195+
return new InMemoryUserDetailsManager();
196+
}
197+
198+
@Bean
199+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
200+
return http.formLogin(Customizer.withDefaults())
201+
.webAuthn((webauthn) -> webauthn.rpId("example.com").rpName("Test RP Name"))
202+
.build();
141203
}
142204

143205
}

docs/modules/ROOT/pages/servlet/authentication/passkeys.adoc

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ SecurityFilterChain filterChain(HttpSecurity http) {
6464
// ...
6565
.formLogin(withDefaults())
6666
.webAuthn((webAuthn) -> webAuthn
67-
.rpName("Spring Security Relying Party")
6867
.rpId("example.com")
6968
.allowedOrigins("https://example.com")
7069
);
@@ -91,7 +90,6 @@ Kotlin::
9190
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
9291
http {
9392
webAuthn {
94-
rpName = "Spring Security Relying Party"
9593
rpId = "example.com"
9694
allowedOrigins = setOf("https://example.com")
9795
}

0 commit comments

Comments
 (0)