diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java index 1a955e523da..7b3f48e04c4 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ * * @param the type of builder * @author Rob Winch + * @author DingHao * @since 6.4 */ public class WebAuthnConfigurer> @@ -130,10 +131,14 @@ public void configure(H http) throws Exception { WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter(); webAuthnAuthnFilter.setAuthenticationManager( new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService))); + webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter); http.addFilterBefore(webAuthnAuthnFilter, BasicAuthenticationFilter.class); - http.addFilterAfter(new WebAuthnRegistrationFilter(userCredentials, rpOperations), AuthorizationFilter.class); - http.addFilterBefore(new PublicKeyCredentialCreationOptionsFilter(rpOperations), AuthorizationFilter.class); - http.addFilterBefore(new PublicKeyCredentialRequestOptionsFilter(rpOperations), AuthorizationFilter.class); + http.addFilterAfter(postProcess(new WebAuthnRegistrationFilter(userCredentials, rpOperations)), + AuthorizationFilter.class); + http.addFilterBefore(postProcess(new PublicKeyCredentialCreationOptionsFilter(rpOperations)), + AuthorizationFilter.class); + http.addFilterBefore(postProcess(new PublicKeyCredentialRequestOptionsFilter(rpOperations)), + AuthorizationFilter.class); DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http .getSharedObject(DefaultLoginPageGeneratingFilter.class); 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 a90c43f3122..e70ffe2a72c 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; @@ -34,6 +36,14 @@ import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; +import org.springframework.security.web.webauthn.authentication.HttpSessionPublicKeyCredentialRequestOptionsRepository; +import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter; +import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsRepository; +import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter; +import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository; +import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsFilter; +import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsRepository; +import org.springframework.security.web.webauthn.registration.WebAuthnRegistrationFilter; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; @@ -126,6 +136,67 @@ public void webauthnWhenConfiguredAndNoDefaultRegistrationPageThenDoesNotServeJa this.mvc.perform(get("/login/webauthn.js")).andExpect(status().isNotFound()); } + @Test + public void configWebauthn() { + this.spring.register(WebauthnFilterConfiguration.class).autowire(); + assertThat(WebauthnFilterConfiguration.count).isEqualTo(4); + } + + @Configuration + @EnableWebSecurity + static class WebauthnFilterConfiguration { + + static int count = 0; + + @Bean + PublicKeyCredentialRequestOptionsRepository requestOptionsRepository() { + return new HttpSessionPublicKeyCredentialRequestOptionsRepository(); + } + + @Bean + PublicKeyCredentialCreationOptionsRepository creationOptionsRepository() { + return new HttpSessionPublicKeyCredentialCreationOptionsRepository(); + } + + @Bean + static BeanPostProcessor beanPostProcessor(PublicKeyCredentialRequestOptionsRepository requestOptionsRepository, + PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) { + return new BeanPostProcessor() { + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof WebAuthnAuthenticationFilter filter) { + filter.setRequestOptionsRepository(requestOptionsRepository); + count++; + } + else if (bean instanceof WebAuthnRegistrationFilter filter) { + filter.setCreationOptionsRepository(creationOptionsRepository); + count++; + } + else if (bean instanceof PublicKeyCredentialCreationOptionsFilter filter) { + filter.setCreationOptionsRepository(creationOptionsRepository); + count++; + } + else if (bean instanceof PublicKeyCredentialRequestOptionsFilter filter) { + filter.setRequestOptionsRepository(requestOptionsRepository); + count++; + } + return bean; + } + }; + } + + @Bean + UserDetailsService userDetailsService() { + return new InMemoryUserDetailsManager(); + } + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.webAuthn(Customizer.withDefaults()).build(); + } + + } + @Configuration @EnableWebSecurity static class DefaultWebauthnConfiguration { diff --git a/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java b/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java index 3f163b0cc2c..b072c262500 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/registration/PublicKeyCredentialCreationOptionsFilter.java @@ -103,4 +103,15 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse this.converter.write(options, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response)); } + /** + * Sets the {@link PublicKeyCredentialCreationOptionsRepository} to use. The default + * is {@link HttpSessionPublicKeyCredentialCreationOptionsRepository}. + * @param creationOptionsRepository the + * {@link PublicKeyCredentialCreationOptionsRepository} to use. Cannot be null. + */ + public void setCreationOptionsRepository(PublicKeyCredentialCreationOptionsRepository creationOptionsRepository) { + Assert.notNull(creationOptionsRepository, "creationOptionsRepository cannot be null"); + this.repository = creationOptionsRepository; + } + }