WebAuthn Publishes Authentication Events

Closes gh-18113

Signed-off-by: suuuuuuminnnnnn <sumin45402214@gmail.com>
This commit is contained in:
Josh Cummings 2026-04-06 13:10:43 -06:00
parent f66fb0814b
commit 0c6b73d123
2 changed files with 68 additions and 2 deletions

View File

@ -24,6 +24,7 @@ import java.util.Set;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.core.authority.FactorGrantedAuthority;
@ -176,8 +177,10 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
WebAuthnRelyingPartyOperations rpOperations = webAuthnRelyingPartyOperations(userEntities, userCredentials);
PublicKeyCredentialCreationOptionsRepository creationOptionsRepository = creationOptionsRepository();
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
webAuthnAuthnFilter.setAuthenticationManager(
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
ProviderManager providerManager = new ProviderManager(
new WebAuthnAuthenticationProvider(rpOperations, userDetailsService));
getBeanOrNull(AuthenticationEventPublisher.class).ifPresent(providerManager::setAuthenticationEventPublisher);
webAuthnAuthnFilter.setAuthenticationManager(providerManager);
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
rpOperations);

View File

@ -30,6 +30,9 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -55,6 +58,7 @@ import org.springframework.security.web.webauthn.management.PublicKeyCredentialU
import org.springframework.security.web.webauthn.management.UserCredentialRepository;
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat;
@ -283,6 +287,55 @@ public class WebAuthnConfigurerTests {
.andExpect(status().isForbidden());
}
@Test
public void webauthnWhenAuthenticationEventPublisherBeanThenUsed() {
this.spring.register(DefaultWebauthnConfiguration.class, CustomEventPublisherConfig.class).autowire();
FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class);
WebAuthnAuthenticationFilter webAuthnFilter = filterChain.getFilterChains()
.get(0)
.getFilters()
.stream()
.filter(WebAuthnAuthenticationFilter.class::isInstance)
.map(WebAuthnAuthenticationFilter.class::cast)
.findFirst()
.orElseThrow();
AuthenticationManager authManager = (AuthenticationManager) ReflectionTestUtils.getField(webAuthnFilter,
"authenticationManager");
assertThat(authManager).isInstanceOf(ProviderManager.class);
Object publisher = ReflectionTestUtils.getField(authManager, "eventPublisher");
AuthenticationEventPublisher expectedPublisher = this.spring.getContext()
.getBean(AuthenticationEventPublisher.class);
assertThat(publisher).isSameAs(expectedPublisher);
}
@Test
public void webauthnWhenNoAuthenticationEventPublisherBeanThenDefaultNullPublisher() {
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class);
WebAuthnAuthenticationFilter webAuthnFilter = filterChain.getFilterChains()
.get(0)
.getFilters()
.stream()
.filter(WebAuthnAuthenticationFilter.class::isInstance)
.map(WebAuthnAuthenticationFilter.class::cast)
.findFirst()
.orElseThrow();
AuthenticationManager authManager = (AuthenticationManager) ReflectionTestUtils.getField(webAuthnFilter,
"authenticationManager");
assertThat(authManager).isInstanceOf(ProviderManager.class);
Object publisher = ReflectionTestUtils.getField(authManager, "eventPublisher");
// ProviderManager.java:316: private static final class NullEventPublisher
assertThat(publisher).isNotNull();
assertThat(publisher.getClass().getDeclaringClass()).isEqualTo(ProviderManager.class);
assertThat(publisher.getClass().getName()).contains("NullEventPublisher");
}
@Configuration
@EnableWebSecurity
static class ConfigCredentialCreationOptionsRepository {
@ -365,6 +418,16 @@ public class WebAuthnConfigurerTests {
}
@Configuration(proxyBeanMethods = false)
static class CustomEventPublisherConfig {
@Bean
AuthenticationEventPublisher authenticationEventPublisher() {
return mock(AuthenticationEventPublisher.class);
}
}
@Configuration(proxyBeanMethods = false)
static class PostProcessorConfiguration {