瀏覽代碼

Detect UserDetailsService bean in X509 configuration

Closes gh-11174
Eleftheria Stein 3 年之前
父節點
當前提交
5ac5edc2e6

+ 30 - 3
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");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with 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 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.AuthenticationDetailsSource;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 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.Authentication;
 import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
 import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
 import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
 import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.web.AuthenticationEntryPoint;
 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.Http403ForbiddenEntryPoint;
 import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
 import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
 import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
 import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
@@ -141,7 +145,9 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
 	/**
 	/**
 	 * Specifies the {@link AuthenticationUserDetailsService} to use. If not specified,
 	 * Specifies the {@link AuthenticationUserDetailsService} to use. If not specified,
 	 * the shared {@link UserDetailsService} will be used to create a
 	 * 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
 	 * @param authenticationUserDetailsService the
 	 * {@link AuthenticationUserDetailsService} to use
 	 * {@link AuthenticationUserDetailsService} to use
 	 * @return the {@link X509Configurer} for further customizations
 	 * @return the {@link X509Configurer} for further customizations
@@ -200,9 +206,30 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
 	private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getAuthenticationUserDetailsService(
 	private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getAuthenticationUserDetailsService(
 			H http) {
 			H http) {
 		if (this.authenticationUserDetailsService == null) {
 		if (this.authenticationUserDetailsService == null) {
-			userDetailsService(http.getSharedObject(UserDetailsService.class));
+			userDetailsService(getSharedOrBean(http, UserDetailsService.class));
 		}
 		}
 		return this.authenticationUserDetailsService;
 		return this.authenticationUserDetailsService;
 	}
 	}
 
 
+	private <C> C getSharedOrBean(H http, Class<C> type) {
+		C shared = http.getSharedObject(type);
+		if (shared != null) {
+			return shared;
+		}
+		return getBeanOrNull(type);
+	}
+
+	private <T> T getBeanOrNull(Class<T> type) {
+		ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
+		if (context == null) {
+			return null;
+		}
+		try {
+			return context.getBean(type);
+		}
+		catch (NoSuchBeanDefinitionException ex) {
+			return null;
+		}
+	}
+
 }
 }

+ 81 - 1
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");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with 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.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 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.security.web.authentication.preauth.x509.X509AuthenticationFilter;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MockMvc;
 
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verify;
 import static org.springframework.security.config.Customizer.withDefaults;
 import static org.springframework.security.config.Customizer.withDefaults;
@@ -95,6 +100,26 @@ public class X509ConfigurerTests {
 		// @formatter:on
 		// @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 extends Certificate> T loadCert(String location) {
 	private <T extends Certificate> T loadCert(String location) {
 		try (InputStream is = new ClassPathResource(location).getInputStream()) {
 		try (InputStream is = new ClassPathResource(location).getInputStream()) {
 			CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
 			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);
+		}
+
+	}
+
 }
 }