Skip to content

Commit f4491f3

Browse files
kse-musicrwinch
authored andcommitted
Set PublicKeyCredentialCreationOptionsRepository by DSL or Bean
Closes gh-16369 Signed-off-by: DingHao <dh.hiekn@gmail.com>
1 parent 4dc1dcb commit f4491f3

File tree

3 files changed

+140
-3
lines changed

3 files changed

+140
-3
lines changed

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

+27
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.springframework.security.web.webauthn.management.Webauthn4JRelyingPartyOperations;
4545
import org.springframework.security.web.webauthn.registration.DefaultWebAuthnRegistrationPageGeneratingFilter;
4646
import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsFilter;
47+
import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsRepository;
4748
import org.springframework.security.web.webauthn.registration.WebAuthnRegistrationFilter;
4849

4950
/**
@@ -64,6 +65,8 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
6465

6566
private boolean disableDefaultRegistrationPage = false;
6667

68+
private PublicKeyCredentialCreationOptionsRepository creationOptionsRepository;
69+
6770
private HttpMessageConverter<Object> converter;
6871

6972
/**
@@ -130,6 +133,17 @@ public WebAuthnConfigurer<H> messageConverter(HttpMessageConverter<Object> conve
130133
return this;
131134
}
132135

136+
/**
137+
* Sets PublicKeyCredentialCreationOptionsRepository
138+
* @param creationOptionsRepository the creationOptionsRepository
139+
* @return the {@link WebAuthnConfigurer} for further customization
140+
*/
141+
public WebAuthnConfigurer<H> creationOptionsRepository(
142+
PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) {
143+
this.creationOptionsRepository = creationOptionsRepository;
144+
return this;
145+
}
146+
133147
@Override
134148
public void configure(H http) throws Exception {
135149
UserDetailsService userDetailsService = getSharedOrBean(http, UserDetailsService.class).orElseGet(() -> {
@@ -141,13 +155,18 @@ public void configure(H http) throws Exception {
141155
UserCredentialRepository userCredentials = getSharedOrBean(http, UserCredentialRepository.class)
142156
.orElse(userCredentialRepository());
143157
WebAuthnRelyingPartyOperations rpOperations = webAuthnRelyingPartyOperations(userEntities, userCredentials);
158+
PublicKeyCredentialCreationOptionsRepository creationOptionsRepository = creationOptionsRepository();
144159
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
145160
webAuthnAuthnFilter.setAuthenticationManager(
146161
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
147162
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
148163
rpOperations);
149164
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
150165
rpOperations);
166+
if (creationOptionsRepository != null) {
167+
webAuthnRegistrationFilter.setCreationOptionsRepository(creationOptionsRepository);
168+
creationOptionsFilter.setCreationOptionsRepository(creationOptionsRepository);
169+
}
151170
if (this.converter != null) {
152171
webAuthnRegistrationFilter.setConverter(this.converter);
153172
creationOptionsFilter.setConverter(this.converter);
@@ -181,6 +200,14 @@ public void configure(H http) throws Exception {
181200
}
182201
}
183202

203+
private PublicKeyCredentialCreationOptionsRepository creationOptionsRepository() {
204+
if (this.creationOptionsRepository != null) {
205+
return this.creationOptionsRepository;
206+
}
207+
ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
208+
return context.getBeanProvider(PublicKeyCredentialCreationOptionsRepository.class).getIfUnique();
209+
}
210+
184211
private <C> Optional<C> getSharedOrBean(H http, Class<C> type) {
185212
C shared = http.getSharedObject(type);
186213
return Optional.ofNullable(shared).or(() -> getBeanOrNull(type));

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

+102-3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
4444
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
4545
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
46+
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
4647
import org.springframework.test.web.servlet.MockMvc;
4748

4849
import static org.assertj.core.api.Assertions.assertThat;
@@ -55,6 +56,7 @@
5556
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
5657
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
5758
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
59+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
5860
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
5961

6062
/**
@@ -141,13 +143,53 @@ public void webauthnWhenConfiguredAndNoDefaultRegistrationPageThenDoesNotServeJa
141143
}
142144

143145
@Test
144-
public void webauthnWhenConfiguredMessageConverter() throws Exception {
146+
public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepository() throws Exception {
147+
TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
148+
SecurityContextHolder.setContext(new SecurityContextImpl(user));
149+
PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions
150+
.createPublicKeyCredentialCreationOptions()
151+
.build();
152+
WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class);
153+
ConfigCredentialCreationOptionsRepository.rpOperations = rpOperations;
154+
given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options);
155+
String attrName = "attrName";
156+
HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository();
157+
creationOptionsRepository.setAttrName(attrName);
158+
ConfigCredentialCreationOptionsRepository.creationOptionsRepository = creationOptionsRepository;
159+
this.spring.register(ConfigCredentialCreationOptionsRepository.class).autowire();
160+
this.mvc.perform(post("/webauthn/register/options"))
161+
.andExpect(status().isOk())
162+
.andExpect(request().sessionAttribute(attrName, options));
163+
}
164+
165+
@Test
166+
public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepositoryBeanPresent() throws Exception {
145167
TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
146168
SecurityContextHolder.setContext(new SecurityContextImpl(user));
147169
PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions
148170
.createPublicKeyCredentialCreationOptions()
149171
.build();
150172
WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class);
173+
ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations = rpOperations;
174+
given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options);
175+
String attrName = "attrName";
176+
HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository();
177+
creationOptionsRepository.setAttrName(attrName);
178+
ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository = creationOptionsRepository;
179+
this.spring.register(ConfigCredentialCreationOptionsRepositoryFromBean.class).autowire();
180+
this.mvc.perform(post("/webauthn/register/options"))
181+
.andExpect(status().isOk())
182+
.andExpect(request().sessionAttribute(attrName, options));
183+
}
184+
185+
@Test
186+
public void webauthnWhenConfiguredMessageConverter() throws Exception {
187+
TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
188+
SecurityContextHolder.setContext(new SecurityContextImpl(user));
189+
PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions
190+
.createPublicKeyCredentialCreationOptions()
191+
.build();
192+
WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class);
151193
ConfigMessageConverter.rpOperations = rpOperations;
152194
given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options);
153195
HttpMessageConverter<Object> converter = mock(HttpMessageConverter.class);
@@ -161,8 +203,65 @@ public void webauthnWhenConfiguredMessageConverter() throws Exception {
161203
ConfigMessageConverter.converter = converter;
162204
this.spring.register(ConfigMessageConverter.class).autowire();
163205
this.mvc.perform(post("/webauthn/register/options"))
164-
.andExpect(status().isOk())
165-
.andExpect(content().string(expectedBody));
206+
.andExpect(status().isOk())
207+
.andExpect(content().string(expectedBody));
208+
}
209+
210+
@Configuration
211+
@EnableWebSecurity
212+
static class ConfigCredentialCreationOptionsRepository {
213+
214+
private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository;
215+
216+
private static WebAuthnRelyingPartyOperations rpOperations;
217+
218+
@Bean
219+
WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
220+
return ConfigCredentialCreationOptionsRepository.rpOperations;
221+
}
222+
223+
@Bean
224+
UserDetailsService userDetailsService() {
225+
return new InMemoryUserDetailsManager();
226+
}
227+
228+
@Bean
229+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
230+
return http.csrf(AbstractHttpConfigurer::disable)
231+
.webAuthn((c) -> c.creationOptionsRepository(creationOptionsRepository))
232+
.build();
233+
}
234+
235+
}
236+
237+
@Configuration
238+
@EnableWebSecurity
239+
static class ConfigCredentialCreationOptionsRepositoryFromBean {
240+
241+
private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository;
242+
243+
private static WebAuthnRelyingPartyOperations rpOperations;
244+
245+
@Bean
246+
WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
247+
return ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations;
248+
}
249+
250+
@Bean
251+
UserDetailsService userDetailsService() {
252+
return new InMemoryUserDetailsManager();
253+
}
254+
255+
@Bean
256+
HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() {
257+
return ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository;
258+
}
259+
260+
@Bean
261+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
262+
return http.csrf(AbstractHttpConfigurer::disable).webAuthn(Customizer.withDefaults()).build();
263+
}
264+
166265
}
167266

168267
@Configuration

web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java

+11
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,17 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
105105
this.converter.write(options, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));
106106
}
107107

108+
/**
109+
* Sets the {@link PublicKeyCredentialCreationOptionsRepository} to use. The default
110+
* is {@link HttpSessionPublicKeyCredentialCreationOptionsRepository}.
111+
* @param creationOptionsRepository the
112+
* {@link PublicKeyCredentialCreationOptionsRepository} to use. Cannot be null.
113+
*/
114+
public void setCreationOptionsRepository(PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) {
115+
Assert.notNull(creationOptionsRepository, "creationOptionsRepository cannot be null");
116+
this.repository = creationOptionsRepository;
117+
}
118+
108119
/**
109120
* Set the {@link HttpMessageConverter} to read the
110121
* {@link WebAuthnRegistrationFilter.WebAuthnRegistrationRequest} and write the

0 commit comments

Comments
 (0)