diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java index 16a2e4e0ea7..ee609d11b1f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java @@ -32,7 +32,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; @@ -42,6 +41,8 @@ import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityConfigurer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; /** @@ -57,8 +58,8 @@ import org.springframework.stereotype.Component; */ @Configuration @ConditionalOnBean(ObjectPostProcessor.class) -@ConditionalOnMissingBean(AuthenticationManager.class) -@Order(Ordered.LOWEST_PRECEDENCE - 3) +@ConditionalOnMissingBean({ AuthenticationManager.class }) +@Order(0) public class AuthenticationManagerConfiguration extends GlobalAuthenticationConfigurerAdapter { @@ -84,18 +85,27 @@ public class AuthenticationManagerConfiguration extends @Bean @Primary - public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth) - throws Exception { + public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth, + ApplicationContext context) throws Exception { + + if (isAuthenticationManagerAlreadyConfigured(context)) { + return new LazyAuthenticationManager(auth); + } + /* * This AuthenticationManagerBuilder is for the global AuthenticationManager */ BootDefaultingAuthenticationConfigurerAdapter configurer = new BootDefaultingAuthenticationConfigurerAdapter(); - configurer.init(auth); configurer.configure(auth); AuthenticationManager manager = configurer.getAuthenticationManagerBuilder() .getOrBuild(); configurer.configureParent(auth); return manager; + + } + + private boolean isAuthenticationManagerAlreadyConfigured(ApplicationContext context) { + return context.getBeanNamesForType(GlobalAuthenticationConfigurerAdapter.class).length > 2; } @Component @@ -142,8 +152,7 @@ public class AuthenticationManagerConfiguration extends * methods are invoked before configure, which cannot be guaranteed at this point. * */ - private class BootDefaultingAuthenticationConfigurerAdapter extends - GlobalAuthenticationConfigurerAdapter { + private class BootDefaultingAuthenticationConfigurerAdapter { private AuthenticationManagerBuilder defaultAuth; @@ -159,7 +168,6 @@ public class AuthenticationManagerConfiguration extends return this.defaultAuth; } - @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { if (auth.isConfigured()) { this.defaultAuth = auth; @@ -188,4 +196,20 @@ public class AuthenticationManagerConfiguration extends } } + private static class LazyAuthenticationManager implements AuthenticationManager { + + private AuthenticationManagerBuilder builder; + + public LazyAuthenticationManager(AuthenticationManagerBuilder builder) { + this.builder = builder; + } + + @Override + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + return builder.getOrBuild().authenticate(authentication); + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfigurationTests.java index 112a6e03f42..179aeb6703a 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfigurationTests.java @@ -16,11 +16,17 @@ package org.springframework.boot.autoconfigure.security; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.junit.After; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; @@ -32,6 +38,7 @@ import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.mock.web.MockServletContext; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.security.authentication.AuthenticationManager; @@ -40,17 +47,16 @@ import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; +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.AuthenticationException; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - /** * Tests for {@link SecurityAutoConfiguration}. * @@ -138,7 +144,8 @@ public class SecurityAutoConfigurationTests { catch (BadCredentialsException e) { // expected } - assertTrue(wrapper.get() instanceof AuthenticationFailureBadCredentialsEvent); + assertTrue("Wrong event type: " + wrapper.get(), + wrapper.get() instanceof AuthenticationFailureBadCredentialsEvent); } @Test @@ -154,6 +161,55 @@ public class SecurityAutoConfigurationTests { this.context.getBean(AuthenticationManager.class)); } + @Test + public void testOverrideAuthenticationManagerAndInjectIntoSecurityFilter() + throws Exception { + this.context = new AnnotationConfigWebApplicationContext(); + this.context.setServletContext(new MockServletContext()); + this.context.register(TestAuthenticationConfiguration.class, + SecurityCustomizer.class, SecurityAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertEquals( + this.context.getBean(TestAuthenticationConfiguration.class).authenticationManager, + this.context.getBean(AuthenticationManager.class)); + } + + @Test + public void testOverrideAuthenticationManagerWithBuilderAndInjectIntoSecurityFilter() + throws Exception { + this.context = new AnnotationConfigWebApplicationContext(); + this.context.setServletContext(new MockServletContext()); + this.context.register(AuthenticationManagerCustomizer.class, + SecurityCustomizer.class, SecurityAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken( + "foo", "bar", + AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); + assertNotNull(this.context.getBean(AuthenticationManager.class) + .authenticate(user)); + } + + @Test + public void testOverrideAuthenticationManagerWithBuilderAndInjectBuilderIntoSecurityFilter() + throws Exception { + this.context = new AnnotationConfigWebApplicationContext(); + this.context.setServletContext(new MockServletContext()); + this.context.register(AuthenticationManagerCustomizer.class, + WorkaroundSecurityCustomizer.class, SecurityAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken( + "foo", "bar", + AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); + assertNotNull(this.context.getBean(AuthenticationManager.class) + .authenticate(user)); + } + @Test public void testJpaCoexistsHappily() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); @@ -196,4 +252,48 @@ public class SecurityAutoConfigurationTests { } + @Configuration + protected static class SecurityCustomizer extends WebSecurityConfigurerAdapter { + + @Autowired + private AuthenticationManager authenticationManager; + + } + + @Configuration + protected static class WorkaroundSecurityCustomizer extends + WebSecurityConfigurerAdapter { + + @Autowired + private AuthenticationManagerBuilder builder; + + @SuppressWarnings("unused") + private AuthenticationManager authenticationManager; + + @Override + protected void configure(HttpSecurity http) throws Exception { + this.authenticationManager = new AuthenticationManager() { + @Override + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + return WorkaroundSecurityCustomizer.this.builder.getOrBuild() + .authenticate(authentication); + } + }; + } + + } + + @Configuration + @Order(-1) + protected static class AuthenticationManagerCustomizer extends + GlobalAuthenticationConfigurerAdapter { + + @Override + public void init(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication().withUser("foo").password("bar").roles("USER"); + } + + } + }