From de7c452e428d846b0ce88220b7f815749e9cffc8 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 22 Oct 2024 14:54:02 +0200 Subject: [PATCH] webauthn: use DefaultResourcesFilter#webauthn - Unconditionally use the DefaultResourcesFilter, because the javascript file is required by the DefaultWebAythnPageGeneratingFilter, which is always registered. --- .../web/configurers/WebAuthnConfigurer.java | 25 +----- .../configurers/WebAuthnConfigurerTests.java | 77 +++++++++++++++++++ 2 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java 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 7403131432..0969cec2f3 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 @@ -16,7 +16,6 @@ package org.springframework.security.config.annotation.web.configurers; -import java.lang.reflect.Constructor; import java.util.HashSet; import java.util.Map; import java.util.Optional; @@ -24,9 +23,6 @@ import java.util.Set; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; -import org.springframework.core.io.ClassPathResource; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.core.userdetails.UserDetailsService; @@ -35,8 +31,6 @@ import org.springframework.security.web.authentication.ui.DefaultLoginPageGenera import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity; import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter; import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter; @@ -51,8 +45,6 @@ import org.springframework.security.web.webauthn.registration.DefaultWebAuthnReg import org.springframework.security.web.webauthn.registration.PublicKeyCredentialCreationOptionsFilter; import org.springframework.security.web.webauthn.registration.WebAuthnRegistrationFilter; -import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; - /** * Configures WebAuthn for Spring Security applications * @@ -133,23 +125,12 @@ public class WebAuthnConfigurer> DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http .getSharedObject(DefaultLoginPageGeneratingFilter.class); if (loginPageGeneratingFilter != null) { - ClassPathResource webauthn = new ClassPathResource( - "org/springframework/security/spring-security-webauthn.js"); - AntPathRequestMatcher matcher = antMatcher(HttpMethod.GET, "/login/webauthn.js"); - - Constructor constructor = DefaultResourcesFilter.class - .getDeclaredConstructor(RequestMatcher.class, ClassPathResource.class, MediaType.class); - constructor.setAccessible(true); - DefaultResourcesFilter resourcesFilter = constructor.newInstance(matcher, webauthn, - MediaType.parseMediaType("text/javascript")); - http.addFilter(resourcesFilter); - DefaultLoginPageGeneratingFilter loginGeneratingFilter = http - .getSharedObject(DefaultLoginPageGeneratingFilter.class); - loginGeneratingFilter.setPasskeysEnabled(true); - loginGeneratingFilter.setResolveHeaders((request) -> { + loginPageGeneratingFilter.setPasskeysEnabled(true); + loginPageGeneratingFilter.setResolveHeaders((request) -> { CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); return Map.of(csrfToken.getHeaderName(), csrfToken.getToken()); }); + http.addFilter(DefaultResourcesFilter.webauthn()); } } 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 new file mode 100644 index 0000000000..6db25c4717 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.web.configurers; + +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.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.test.SpringTestContext; +import org.springframework.security.config.test.SpringTestContextExtension; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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; + +/** + * @author Daniel Garnier-Moiroux + */ +@ExtendWith(SpringTestContextExtension.class) +public class WebAuthnConfigurerTests { + + public final SpringTestContext spring = new SpringTestContext(this); + + @Autowired + MockMvc mvc; + + @Test + public void javascriptWhenWebauthnConfiguredThenServesJavascript() throws Exception { + this.spring.register(DefaultWebauthnConfiguration.class).autowire(); + this.mvc.perform(get("/login/webauthn.js")) + .andExpect(status().isOk()) + .andExpect(header().string("content-type", "text/javascript;charset=UTF-8")) + .andExpect(content().string(containsString("async function authenticate("))); + } + + @Configuration + @EnableWebSecurity + static class DefaultWebauthnConfiguration { + + @Bean + UserDetailsService userDetailsService() { + return new InMemoryUserDetailsManager(); + } + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.webAuthn(Customizer.withDefaults()).build(); + } + + } + +}