Skip to content

Commit 5129fea

Browse files
committed
Apply SessionAuthenticationStrategy to WebAuthnAuthenticationFilter
WebAuthnConfigurer did not wire the shared SessionAuthenticationStrategy into WebAuthnAuthenticationFilter, so session concurrency limits were bypassed during passkey authentication. Closes gh-16685 Signed-off-by: hanweiwei <duzielww@163.com> Made-with: Cursor
1 parent 1455798 commit 5129fea

2 files changed

Lines changed: 53 additions & 2 deletions

File tree

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.security.web.AuthenticationEntryPoint;
3232
import org.springframework.security.web.access.intercept.AuthorizationFilter;
3333
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
34+
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
3435
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
3536
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
3637
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@@ -170,14 +171,19 @@ public void configure(H http) {
170171
.orElseThrow(() -> new IllegalStateException("Missing UserDetailsService Bean"));
171172
PublicKeyCredentialUserEntityRepository userEntities = getSharedOrBean(http,
172173
PublicKeyCredentialUserEntityRepository.class)
173-
.orElse(userEntityRepository());
174+
.orElseGet(this::userEntityRepository);
174175
UserCredentialRepository userCredentials = getSharedOrBean(http, UserCredentialRepository.class)
175-
.orElse(userCredentialRepository());
176+
.orElseGet(this::userCredentialRepository);
176177
WebAuthnRelyingPartyOperations rpOperations = webAuthnRelyingPartyOperations(userEntities, userCredentials);
177178
PublicKeyCredentialCreationOptionsRepository creationOptionsRepository = creationOptionsRepository();
178179
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
179180
webAuthnAuthnFilter.setAuthenticationManager(
180181
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
182+
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
183+
.getSharedObject(SessionAuthenticationStrategy.class);
184+
if (sessionAuthenticationStrategy != null) {
185+
webAuthnAuthnFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
186+
}
181187
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
182188
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
183189
rpOperations);

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
4343
import org.springframework.security.web.FilterChainProxy;
4444
import org.springframework.security.web.SecurityFilterChain;
45+
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
4546
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
4647
import org.springframework.security.web.webauthn.api.Bytes;
4748
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
@@ -408,6 +409,50 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
408409

409410
}
410411

412+
// gh-16685
413+
@Test
414+
public void webAuthnWhenSessionManagementConfiguredThenSessionAuthenticationStrategyApplied() {
415+
this.spring.register(SessionManagementWebauthnConfiguration.class).autowire();
416+
FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class);
417+
List<jakarta.servlet.Filter> filters = filterChain.getFilterChains().get(0).getFilters();
418+
WebAuthnAuthenticationFilter webAuthnFilter = filters.stream()
419+
.filter(WebAuthnAuthenticationFilter.class::isInstance)
420+
.map(WebAuthnAuthenticationFilter.class::cast)
421+
.findFirst()
422+
.orElseThrow(() -> new AssertionError("WebAuthnAuthenticationFilter not found"));
423+
SessionAuthenticationStrategy strategy = (SessionAuthenticationStrategy) org.springframework.test.util.ReflectionTestUtils
424+
.getField(webAuthnFilter, "sessionStrategy");
425+
assertThat(strategy).isNotNull();
426+
assertThat(strategy).isInstanceOf(
427+
org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy.class);
428+
}
429+
430+
@Configuration
431+
@EnableWebSecurity
432+
static class SessionManagementWebauthnConfiguration {
433+
434+
@Bean
435+
UserDetailsService userDetailsService() {
436+
return new InMemoryUserDetailsManager();
437+
}
438+
439+
@Bean
440+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
441+
// @formatter:off
442+
http
443+
.formLogin(Customizer.withDefaults())
444+
.sessionManagement((session) -> session
445+
.maximumSessions(1)
446+
)
447+
.webAuthn((authn) -> authn
448+
.rpId("example.com")
449+
);
450+
// @formatter:on
451+
return http.build();
452+
}
453+
454+
}
455+
411456
@Configuration
412457
@EnableWebSecurity
413458
static class CustomRpNameWebauthnConfiguration {

0 commit comments

Comments
 (0)