Skip to content

Commit 462ecb5

Browse files
committed
Set PublicKeyCredentialCreationOptionsRepository by DSL or Bean
Closes spring-projectsgh-16369 Signed-off-by: DingHao <[email protected]>
1 parent 72a2831 commit 462ecb5

File tree

3 files changed

+153
-2
lines changed

3 files changed

+153
-2
lines changed

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.springframework.security.web.webauthn.management.Webauthn4JRelyingPartyOperations;
4444
import org.springframework.security.web.webauthn.registration.DefaultWebAuthnRegistrationPageGeneratingFilter;
4545
import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsFilter;
46+
import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsRepository;
4647
import org.springframework.security.web.webauthn.registration.WebAuthnRegistrationFilter;
4748

4849
/**
@@ -63,6 +64,8 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
6364

6465
private boolean disableDefaultRegistrationPage = false;
6566

67+
private PublicKeyCredentialCreationOptionsRepository creationOptionsRepository;
68+
6669
/**
6770
* The Relying Party id.
6871
* @param rpId the relying party id
@@ -116,6 +119,17 @@ public WebAuthnConfigurer<H> disableDefaultRegistrationPage(boolean disable) {
116119
return this;
117120
}
118121

122+
/**
123+
* Sets PublicKeyCredentialCreationOptionsRepository
124+
* @param creationOptionsRepository the creationOptionsRepository
125+
* @return the {@link WebAuthnConfigurer} for further customization
126+
*/
127+
public WebAuthnConfigurer<H> creationOptionsRepository(
128+
PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) {
129+
this.creationOptionsRepository = creationOptionsRepository;
130+
return this;
131+
}
132+
119133
@Override
120134
public void configure(H http) throws Exception {
121135
UserDetailsService userDetailsService = getSharedOrBean(http, UserDetailsService.class).orElseGet(() -> {
@@ -127,12 +141,21 @@ public void configure(H http) throws Exception {
127141
UserCredentialRepository userCredentials = getSharedOrBean(http, UserCredentialRepository.class)
128142
.orElse(userCredentialRepository());
129143
WebAuthnRelyingPartyOperations rpOperations = webAuthnRelyingPartyOperations(userEntities, userCredentials);
144+
PublicKeyCredentialCreationOptionsRepository creationOptionsRepository = creationOptionsRepository();
130145
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
131146
webAuthnAuthnFilter.setAuthenticationManager(
132147
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
148+
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
149+
rpOperations);
150+
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
151+
rpOperations);
152+
if (creationOptionsRepository != null) {
153+
webAuthnRegistrationFilter.setCreationOptionsRepository(creationOptionsRepository);
154+
creationOptionsFilter.setCreationOptionsRepository(creationOptionsRepository);
155+
}
133156
http.addFilterBefore(webAuthnAuthnFilter, BasicAuthenticationFilter.class);
134-
http.addFilterAfter(new WebAuthnRegistrationFilter(userCredentials, rpOperations), AuthorizationFilter.class);
135-
http.addFilterBefore(new PublicKeyCredentialCreationOptionsFilter(rpOperations), AuthorizationFilter.class);
157+
http.addFilterAfter(webAuthnRegistrationFilter, AuthorizationFilter.class);
158+
http.addFilterBefore(creationOptionsFilter, AuthorizationFilter.class);
136159
http.addFilterBefore(new PublicKeyCredentialRequestOptionsFilter(rpOperations), AuthorizationFilter.class);
137160

138161
DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http
@@ -159,6 +182,14 @@ public void configure(H http) throws Exception {
159182
}
160183
}
161184

185+
private PublicKeyCredentialCreationOptionsRepository creationOptionsRepository() {
186+
if (this.creationOptionsRepository != null) {
187+
return this.creationOptionsRepository;
188+
}
189+
ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
190+
return context.getBeanProvider(PublicKeyCredentialCreationOptionsRepository.class).getIfUnique();
191+
}
192+
162193
private <C> Optional<C> getSharedOrBean(H http, Class<C> type) {
163194
C shared = http.getSharedObject(type);
164195
return Optional.ofNullable(shared).or(() -> getBeanOrNull(type));

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

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,35 @@
2424
import org.springframework.beans.factory.annotation.Autowired;
2525
import org.springframework.context.annotation.Bean;
2626
import org.springframework.context.annotation.Configuration;
27+
import org.springframework.security.authentication.TestingAuthenticationToken;
2728
import org.springframework.security.config.Customizer;
2829
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2930
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
3031
import org.springframework.security.config.test.SpringTestContext;
3132
import org.springframework.security.config.test.SpringTestContextExtension;
33+
import org.springframework.security.core.context.SecurityContextHolder;
34+
import org.springframework.security.core.context.SecurityContextImpl;
3235
import org.springframework.security.core.userdetails.UserDetailsService;
3336
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
3437
import org.springframework.security.web.FilterChainProxy;
3538
import org.springframework.security.web.SecurityFilterChain;
3639
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
40+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
41+
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
42+
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
43+
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
3744
import org.springframework.test.web.servlet.MockMvc;
3845

3946
import static org.assertj.core.api.Assertions.assertThat;
4047
import static org.hamcrest.Matchers.containsString;
48+
import static org.mockito.ArgumentMatchers.any;
49+
import static org.mockito.BDDMockito.given;
50+
import static org.mockito.Mockito.mock;
4151
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
52+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
4253
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
4354
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
55+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
4456
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
4557

4658
/**
@@ -126,6 +138,103 @@ public void webauthnWhenConfiguredAndNoDefaultRegistrationPageThenDoesNotServeJa
126138
this.mvc.perform(get("/login/webauthn.js")).andExpect(status().isNotFound());
127139
}
128140

141+
@Test
142+
public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepository() throws Exception {
143+
TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
144+
SecurityContextHolder.setContext(new SecurityContextImpl(user));
145+
PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions
146+
.createPublicKeyCredentialCreationOptions()
147+
.build();
148+
WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class);
149+
ConfigCredentialCreationOptionsRepository.rpOperations = rpOperations;
150+
given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options);
151+
String attrName = "attrName";
152+
HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository();
153+
creationOptionsRepository.setAttrName(attrName);
154+
ConfigCredentialCreationOptionsRepository.creationOptionsRepository = creationOptionsRepository;
155+
this.spring.register(ConfigCredentialCreationOptionsRepository.class).autowire();
156+
this.mvc.perform(post("/webauthn/register/options"))
157+
.andExpect(status().isOk())
158+
.andExpect(request().sessionAttribute(attrName, options));
159+
}
160+
161+
@Test
162+
public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepositoryBeanPresent() throws Exception {
163+
TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
164+
SecurityContextHolder.setContext(new SecurityContextImpl(user));
165+
PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions
166+
.createPublicKeyCredentialCreationOptions()
167+
.build();
168+
WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class);
169+
ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations = rpOperations;
170+
given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options);
171+
String attrName = "attrName";
172+
HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository();
173+
creationOptionsRepository.setAttrName(attrName);
174+
ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository = creationOptionsRepository;
175+
this.spring.register(ConfigCredentialCreationOptionsRepositoryFromBean.class).autowire();
176+
this.mvc.perform(post("/webauthn/register/options"))
177+
.andExpect(status().isOk())
178+
.andExpect(request().sessionAttribute(attrName, options));
179+
}
180+
181+
@Configuration
182+
@EnableWebSecurity
183+
static class ConfigCredentialCreationOptionsRepository {
184+
185+
private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository;
186+
187+
private static WebAuthnRelyingPartyOperations rpOperations;
188+
189+
@Bean
190+
WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
191+
return ConfigCredentialCreationOptionsRepository.rpOperations;
192+
}
193+
194+
@Bean
195+
UserDetailsService userDetailsService() {
196+
return new InMemoryUserDetailsManager();
197+
}
198+
199+
@Bean
200+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
201+
return http.csrf(AbstractHttpConfigurer::disable)
202+
.webAuthn((c) -> c.creationOptionsRepository(creationOptionsRepository))
203+
.build();
204+
}
205+
206+
}
207+
208+
@Configuration
209+
@EnableWebSecurity
210+
static class ConfigCredentialCreationOptionsRepositoryFromBean {
211+
212+
private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository;
213+
214+
private static WebAuthnRelyingPartyOperations rpOperations;
215+
216+
@Bean
217+
WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
218+
return ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations;
219+
}
220+
221+
@Bean
222+
UserDetailsService userDetailsService() {
223+
return new InMemoryUserDetailsManager();
224+
}
225+
226+
@Bean
227+
HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() {
228+
return ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository;
229+
}
230+
231+
@Bean
232+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
233+
return http.csrf(AbstractHttpConfigurer::disable).webAuthn(Customizer.withDefaults()).build();
234+
}
235+
236+
}
237+
129238
@Configuration
130239
@EnableWebSecurity
131240
static class DefaultWebauthnConfiguration {

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,15 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
103103
this.converter.write(options, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));
104104
}
105105

106+
/**
107+
* Sets the {@link PublicKeyCredentialCreationOptionsRepository} to use. The default
108+
* is {@link HttpSessionPublicKeyCredentialCreationOptionsRepository}.
109+
* @param creationOptionsRepository the
110+
* {@link PublicKeyCredentialCreationOptionsRepository} to use. Cannot be null.
111+
*/
112+
public void setCreationOptionsRepository(PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) {
113+
Assert.notNull(creationOptionsRepository, "creationOptionsRepository cannot be null");
114+
this.repository = creationOptionsRepository;
115+
}
116+
106117
}

0 commit comments

Comments
 (0)