diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java index 39bbba45c4..046fd5af8a 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java +++ b/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; diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeAuthenticationProviderBeanManagerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeAuthenticationProviderBeanManagerConfigurer.java new file mode 100644 index 0000000000..d6da5b53c3 --- /dev/null +++ b/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 getBeanOrNull(Class type) { + String[] userDetailsBeanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context + .getBeanNamesForType(type); + if (userDetailsBeanNames.length != 1) { + return null; + } + + return InitializeAuthenticationProviderBeanManagerConfigurer.this.context + .getBean(userDetailsBeanNames[0], type); + } + } +} \ No newline at end of file diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeUserDetailsBeanManagerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeUserDetailsBeanManagerConfigurer.java index 8bdaf03143..8f66c2d61b 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeUserDetailsBeanManagerConfigurer.java +++ b/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 getBeanOrNull(Class 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); } } } \ No newline at end of file diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfigurationTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfigurationTests.groovy index cab0599856..9f7401f357 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfigurationTests.groovy +++ b/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 + } + } } \ No newline at end of file diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index 2142da6f2d..7f72b237d9 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -378,6 +378,7 @@ This will give you access to the entire project history (including all releases * <> * <> * <> +* <> === 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(); } ----