diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java index 93e1b09250..30de7141b4 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -18,15 +18,19 @@ package org.springframework.security.config.annotation.web.configurers; import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; @@ -141,7 +145,9 @@ public final class X509Configurer> /** * Specifies the {@link AuthenticationUserDetailsService} to use. If not specified, * the shared {@link UserDetailsService} will be used to create a - * {@link UserDetailsByNameServiceWrapper}. + * {@link UserDetailsByNameServiceWrapper}. If a {@link SecurityFilterChain} bean is + * used instead of the {@link WebSecurityConfigurerAdapter}, then the + * {@link UserDetailsService} bean will be used by default. * @param authenticationUserDetailsService the * {@link AuthenticationUserDetailsService} to use * @return the {@link X509Configurer} for further customizations @@ -200,9 +206,30 @@ public final class X509Configurer> private AuthenticationUserDetailsService getAuthenticationUserDetailsService( H http) { if (this.authenticationUserDetailsService == null) { - userDetailsService(http.getSharedObject(UserDetailsService.class)); + userDetailsService(getSharedOrBean(http, UserDetailsService.class)); } return this.authenticationUserDetailsService; } + private C getSharedOrBean(H http, Class type) { + C shared = http.getSharedObject(type); + if (shared != null) { + return shared; + } + return getBeanOrNull(type); + } + + private T getBeanOrNull(Class type) { + ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class); + if (context == null) { + return null; + } + try { + return context.getBean(type); + } + catch (NoSuchBeanDefinitionException ex) { + return null; + } + } + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java index ebcfb0ec96..e2bc35bbd1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 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. @@ -34,10 +34,15 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.test.web.servlet.MockMvc; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; @@ -95,6 +100,26 @@ public class X509ConfigurerTests { // @formatter:on } + @Test + public void x509WhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception { + this.spring.register(UserDetailsServiceBeanConfig.class).autowire(); + X509Certificate certificate = loadCert("rod.cer"); + // @formatter:off + this.mvc.perform(get("/").with(x509(certificate))) + .andExpect(authenticated().withUsername("rod")); + // @formatter:on + } + + @Test + public void x509WhenUserDetailsServiceAndBeanConfiguredThenDoesNotUseBean() throws Exception { + this.spring.register(UserDetailsServiceAndBeanConfig.class).autowire(); + X509Certificate certificate = loadCert("rod.cer"); + // @formatter:off + this.mvc.perform(get("/").with(x509(certificate))) + .andExpect(authenticated().withUsername("rod")); + // @formatter:on + } + private T loadCert(String location) { try (InputStream is = new ClassPathResource(location).getInputStream()) { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); @@ -206,4 +231,59 @@ public class X509ConfigurerTests { } + @EnableWebSecurity + static class UserDetailsServiceBeanConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .x509(withDefaults()); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService userDetailsService() { + // @formatter:off + return new InMemoryUserDetailsManager( + User.withDefaultPasswordEncoder() + .username("rod") + .password("password") + .roles("USER", "ADMIN") + .build() + ); + // @formatter:on + } + + } + + @EnableWebSecurity + static class UserDetailsServiceAndBeanConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + UserDetailsService customUserDetailsService = new InMemoryUserDetailsManager( + User.withDefaultPasswordEncoder() + .username("rod") + .password("password") + .roles("USER", "ADMIN") + .build()); + http + .x509((x509) -> x509 + .userDetailsService(customUserDetailsService) + ); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService userDetailsService() { + // @formatter:off + return mock(UserDetailsService.class); + } + + } + }