Răsfoiți Sursa

Allow AuthenticationProvider Bean in Java Config

This commit adds support for defaulting java configuration's
authentication by providing an AuthenticationProvider Bean.

Fixes gh-3091
Rob Winch 9 ani în urmă
părinte
comite
4b650dc58d

+ 5 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java

@@ -73,6 +73,11 @@ public class AuthenticationConfiguration {
 		return new InitializeUserDetailsBeanManagerConfigurer(context);
 	}
 
+	@Bean
+	public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
+		return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
+	}
+
 	public AuthenticationManager getAuthenticationManager() throws Exception {
 		if (authenticationManagerInitialized) {
 			return authenticationManager;

+ 87 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeAuthenticationProviderBeanManagerConfigurer.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2015 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
+ *
+ *      http://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.authentication.configuration;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+/**
+ * Lazily initializes the global authentication with a {@link UserDetailsService} if it is
+ * not yet configured and there is only a single Bean of that type. Optionally, if a
+ * {@link PasswordEncoder} is defined will wire this up too.
+ *
+ * @author Rob Winch
+ * @since 4.1
+ */
+@Order(InitializeAuthenticationProviderBeanManagerConfigurer.DEFAULT_ORDER)
+class InitializeAuthenticationProviderBeanManagerConfigurer
+		extends GlobalAuthenticationConfigurerAdapter {
+
+	static final int DEFAULT_ORDER = InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER
+			- 100;
+
+	private final ApplicationContext context;
+
+	/**
+	 * @param context the ApplicationContext to look up beans.
+	 */
+	public InitializeAuthenticationProviderBeanManagerConfigurer(
+			ApplicationContext context) {
+		this.context = context;
+	}
+
+	@Override
+	public void init(AuthenticationManagerBuilder auth) throws Exception {
+		auth.apply(new InitializeUserDetailsManagerConfigurer());
+	}
+
+	class InitializeUserDetailsManagerConfigurer
+			extends GlobalAuthenticationConfigurerAdapter {
+		@Override
+		public void configure(AuthenticationManagerBuilder auth) throws Exception {
+			if (auth.isConfigured()) {
+				return;
+			}
+			AuthenticationProvider authenticationProvider = getBeanOrNull(
+					AuthenticationProvider.class);
+			if (authenticationProvider == null) {
+				return;
+			}
+
+
+			auth.authenticationProvider(authenticationProvider);
+		}
+
+		/**
+		 * @return
+		 */
+		private <T> T getBeanOrNull(Class<T> type) {
+			String[] userDetailsBeanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
+					.getBeanNamesForType(type);
+			if (userDetailsBeanNames.length != 1) {
+				return null;
+			}
+
+			return InitializeAuthenticationProviderBeanManagerConfigurer.this.context
+					.getBean(userDetailsBeanNames[0], type);
+		}
+	}
+}

+ 20 - 15
config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeUserDetailsBeanManagerConfigurer.java

@@ -25,16 +25,18 @@ import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.crypto.password.PasswordEncoder;
 
 /**
- * Lazily initializes the global authentication with a
- * {@link UserDetailsService} if it is not yet configured and there is only a
- * single Bean of that type. Optionally, if a {@link PasswordEncoder} is defined
- * will wire this up too.
+ * Lazily initializes the global authentication with a {@link UserDetailsService} if it is
+ * not yet configured and there is only a single Bean of that type. Optionally, if a
+ * {@link PasswordEncoder} is defined will wire this up too.
  *
  * @author Rob Winch
  * @since 4.1
  */
-@Order(Ordered.LOWEST_PRECEDENCE - 5000)
-class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
+@Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER)
+class InitializeUserDetailsBeanManagerConfigurer
+		extends GlobalAuthenticationConfigurerAdapter {
+
+	static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;
 
 	private final ApplicationContext context;
 
@@ -50,14 +52,16 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
 		auth.apply(new InitializeUserDetailsManagerConfigurer());
 	}
 
-	class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
+	class InitializeUserDetailsManagerConfigurer
+			extends GlobalAuthenticationConfigurerAdapter {
 		@Override
 		public void configure(AuthenticationManagerBuilder auth) throws Exception {
 			if (auth.isConfigured()) {
 				return;
 			}
-			UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class);
-			if(userDetailsService == null) {
+			UserDetailsService userDetailsService = getBeanOrNull(
+					UserDetailsService.class);
+			if (userDetailsService == null) {
 				return;
 			}
 
@@ -65,24 +69,25 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
 
 			DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
 			provider.setUserDetailsService(userDetailsService);
-			if(passwordEncoder != null) {
+			if (passwordEncoder != null) {
 				provider.setPasswordEncoder(passwordEncoder);
 			}
 
-			auth
-				.authenticationProvider(provider);
+			auth.authenticationProvider(provider);
 		}
 
 		/**
 		 * @return
 		 */
 		private <T> T getBeanOrNull(Class<T> type) {
-			String[] userDetailsBeanNames = context.getBeanNamesForType(type);
-			if(userDetailsBeanNames.length != 1) {
+			String[] userDetailsBeanNames = InitializeUserDetailsBeanManagerConfigurer.this.context
+					.getBeanNamesForType(type);
+			if (userDetailsBeanNames.length != 1) {
 				return null;
 			}
 
-			return context.getBean(userDetailsBeanNames[0], type);
+			return InitializeUserDetailsBeanManagerConfigurer.this.context
+					.getBean(userDetailsBeanNames[0], type);
 		}
 	}
 }

+ 62 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfigurationTests.groovy

@@ -24,6 +24,7 @@ import org.springframework.core.Ordered
 import org.springframework.core.annotation.Order
 import org.springframework.security.access.annotation.Secured
 import org.springframework.security.authentication.AuthenticationManager
+import org.springframework.security.authentication.AuthenticationProvider
 import org.springframework.security.authentication.TestingAuthenticationToken
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
 import org.springframework.security.authentication.dao.DaoAuthenticationProvider
@@ -423,4 +424,65 @@ class AuthenticationConfigurationTests extends BaseSpringSpec {
 			new BCryptPasswordEncoder()
 		}
 	}
+
+	def 'gh-3091: Allow Configure AuthenticationProvider'() {
+		setup:
+		AuthenticationProvider ap = Mock()
+		AuthenticationProviderBeanConfig.AP = ap
+		loadConfig(AuthenticationProviderBeanConfig)
+		AuthenticationManager am = context.getBean(AuthenticationConfiguration).getAuthenticationManager()
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("user", "password");
+		User user = new User("user","password",AuthorityUtils.createAuthorityList("ROLE_USER"))
+		when:
+		am.authenticate(token)
+		then:
+		1 * ap.supports(_) >> true
+		1 * ap.authenticate(token) >> new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities())
+	}
+
+	@Configuration
+	@Import([AuthenticationConfiguration, ObjectPostProcessorConfiguration])
+	static class AuthenticationProviderBeanConfig {
+		static AuthenticationProvider AP
+
+		@Bean
+		AuthenticationProvider authenticationProvider() {
+			AP
+		}
+	}
+
+	def 'AuthenticationProvider Bean Prioritized over UserDetailsService'() {
+		setup:
+		UserDetailsService uds = Mock()
+		AuthenticationProvider ap = Mock()
+		AuthenticationProviderBeanAndUserDetailsServiceConfig.AP = ap
+		AuthenticationProviderBeanAndUserDetailsServiceConfig.UDS = uds
+		loadConfig(AuthenticationProviderBeanAndUserDetailsServiceConfig)
+		AuthenticationManager am = context.getBean(AuthenticationConfiguration).getAuthenticationManager()
+		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("user", "password");
+		User user = new User("user","password",AuthorityUtils.createAuthorityList("ROLE_USER"))
+		when:
+		am.authenticate(token)
+		then:
+		1 * ap.supports(_) >> true
+		1 * ap.authenticate(token) >> new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities())
+		0 * uds._
+	}
+
+	@Configuration
+	@Import([AuthenticationConfiguration, ObjectPostProcessorConfiguration])
+	static class AuthenticationProviderBeanAndUserDetailsServiceConfig {
+		static AuthenticationProvider AP
+		static UserDetailsService UDS
+
+		@Bean
+		AuthenticationProvider authenticationProvider() {
+			AP
+		}
+
+		@Bean
+		UserDetailsService uds() {
+			UDS
+		}
+	}
 }

+ 21 - 2
docs/manual/src/docs/asciidoc/index.adoc

@@ -378,6 +378,7 @@ This will give you access to the entire project history (including all releases
 * <<el-access-web-path-variables,Path Variables in Web Security Expressions>>
 * <<test-method-withanonymoususer,@WithAnonymousUser>>
 * <<jc-authentication-userdetailsservice,Simplified UserDetailsService Java Configuration>>
+* <<jc-authentication-authenticationprovider,Simplified AuthenticationProvider Java Configuration>>
 
 === What's new in Spring Security 4.0
 
@@ -916,17 +917,35 @@ cn: admin
 uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
 ----
 
+[[jc-authentication-authenticationprovider]]
+==== AuthenticationProvider
+
+You can define custom authentication by exposing a custom `AuthenticationProvider` as a bean.
+For example, the following will customize authentication assuming that `SpringAuthenticationProvider` implements `AuthenticationProvider`:
+
+NOTE: This is only used if the `AuthenticationManagerBuilder` has not been populated
+
+[source,java]
+----
+@Bean
+public SpringAuthenticationProvider springAuthenticationProvider() {
+	return new SpringAuthenticationProvider();
+}
+----
+
 [[jc-authentication-userdetailsservice]]
 ==== UserDetailsService
 
 You can define custom authentication by exposing a custom `UserDetailsService` as a bean.
 For example, the following will customize authentication assuming that `SpringDataUserDetailsService` implements `UserDetailsService`:
 
+NOTE: This is only used if the `AuthenticationManagerBuilder` has not been populated and no `AuthenticationProviderBean` is defined.
+
 [source,java]
 ----
 @Bean
 public SpringDataUserDetailsService springDataUserDetailsService() {
-		return new SpringDataUserDetailsService();
+	return new SpringDataUserDetailsService();
 }
 ----
 
@@ -937,7 +956,7 @@ For example, if you use bcrypt you can add a bean definition as shown below:
 ----
 @Bean
 public BCryptPasswordEncoder passwordEncoder() {
-		return new BCryptPasswordEncoder();
+	return new BCryptPasswordEncoder();
 }
 ----