From 3d437711363ade1717a7e11b7b781d240bad4983 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 13 Mar 2014 11:51:38 +0000 Subject: [PATCH] Register an AuthenticationManager in security autoconfig This is quite a big step, but I think it helps a lot. Since Spring Boot always creates an AuthenticationManager if it doesn't find one already registered, it makes sense to also make it into a @Bean. Spring Security does not register its AuthenticationManager by default though, so we have to do that for it if the user has created one with an @Autowired AuthenticationManagerBuilder, but not registered it as a @Bean. Having the @Bean (marked @Primary to prevent issues with @Autowired) makes it easier to reason about what Spring Boot has done for you, and easier to default in simple use cases to the boot-created AuthenticationManager. For example, if I want an OAuth2 Authorization Server with password grant, it makes total sense for the AuthenticationManager for users to be the same as the @Primary one. Now it is easy to set that up (just @Autowire it). --- .../condition/SpringBootCondition.java | 17 +++++++--- .../AuthenticationManagerConfiguration.java | 34 +++++++++++++++++-- .../SecurityAutoConfigurationTests.java | 28 ++++++--------- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/SpringBootCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/SpringBootCondition.java index 2de6f8ab7c..085a2d862a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/SpringBootCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/SpringBootCondition.java @@ -40,10 +40,19 @@ public abstract class SpringBootCondition implements Condition { @Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String classOrMethodName = getClassOrMethodName(metadata); - ConditionOutcome outcome = getMatchOutcome(context, metadata); - logOutcome(classOrMethodName, outcome); - recordEvaluation(context, classOrMethodName, outcome); - return outcome.isMatch(); + try { + ConditionOutcome outcome = getMatchOutcome(context, metadata); + logOutcome(classOrMethodName, outcome); + recordEvaluation(context, classOrMethodName, outcome); + return outcome.isMatch(); + } + catch (NoClassDefFoundError e) { + throw new IllegalStateException( + "Could not evaluate condition owing to internal class not found. " + + "This can happen if you are @ComponentScanning a springframework package " + + "(e.g. if you put a @ComponentScan in the default package by mistake)", + e); + } } private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) { 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 b91e0a5473..862af6ec5b 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 @@ -26,7 +26,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.security.SecurityProperties.User; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; @@ -60,9 +65,25 @@ public class AuthenticationManagerConfiguration extends @Autowired private SecurityProperties security; + private BootDefaultingAuthenticationConfigurerAdapter configurer = new BootDefaultingAuthenticationConfigurerAdapter(); + @Override public void init(AuthenticationManagerBuilder auth) throws Exception { - auth.apply(new BootDefaultingAuthenticationConfigurerAdapter()); + auth.apply(this.configurer); + } + + @Bean + // avoid issues with scopedTarget (SPR-11548) + @Primary + public AuthenticationManager authenticationManager() { + return lazyAuthenticationManager(); + } + + @Bean + @Lazy + @Scope(proxyMode = ScopedProxyMode.INTERFACES) + protected AuthenticationManager lazyAuthenticationManager() { + return this.configurer.getAuthenticationManagerBuilder().getOrBuild(); } /** @@ -92,9 +113,16 @@ public class AuthenticationManagerConfiguration extends private class BootDefaultingAuthenticationConfigurerAdapter extends GlobalAuthenticationConfigurerAdapter { + private AuthenticationManagerBuilder defaultAuth; + + public AuthenticationManagerBuilder getAuthenticationManagerBuilder() { + return this.defaultAuth; + } + @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { if (auth.isConfigured()) { + this.defaultAuth = auth; return; } @@ -104,12 +132,12 @@ public class AuthenticationManagerConfiguration extends + user.getPassword() + "\n\n"); } - AuthenticationManagerBuilder defaultAuth = new AuthenticationManagerBuilder( + this.defaultAuth = new AuthenticationManagerBuilder( AuthenticationManagerConfiguration.this.objectPostProcessor); Set roles = new LinkedHashSet(user.getRole()); - AuthenticationManager parent = defaultAuth.inMemoryAuthentication() + AuthenticationManager parent = this.defaultAuth.inMemoryAuthentication() .withUser(user.getName()).password(user.getPassword()) .roles(roles.toArray(new String[roles.size()])).and().and().build(); 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 dbb05a175b..77454d8161 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 @@ -19,15 +19,11 @@ package org.springframework.boot.autoconfigure.security; import java.util.List; import org.junit.Test; -import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.test.City; -import org.springframework.boot.context.event.ApplicationPreparedEvent; -import org.springframework.boot.logging.LoggingApplicationListener; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -60,7 +56,7 @@ public class SecurityAutoConfigurationTests { this.context.setServletContext(new MockServletContext()); this.context.register(SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); - debugRefresh(this.context); + this.context.refresh(); assertNotNull(this.context.getBean(AuthenticationManagerBuilder.class)); // 4 for static resources and one for the rest List filterChains = this.context.getBean( @@ -93,6 +89,16 @@ public class SecurityAutoConfigurationTests { assertEquals(0, this.context.getBeanNamesForType(FilterChainProxy.class).length); } + @Test + public void testAuthenticationManagerCreated() throws Exception { + this.context = new AnnotationConfigWebApplicationContext(); + this.context.setServletContext(new MockServletContext()); + this.context.register(SecurityAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertNotNull(this.context.getBean(AuthenticationManager.class)); + } + @Test public void testOverrideAuthenticationManager() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); @@ -118,18 +124,6 @@ public class SecurityAutoConfigurationTests { assertNotNull(this.context.getBean(JpaTransactionManager.class)); } - private static AnnotationConfigWebApplicationContext debugRefresh( - AnnotationConfigWebApplicationContext context) { - EnvironmentTestUtils.addEnvironment(context, "debug:true"); - LoggingApplicationListener logging = new LoggingApplicationListener(); - logging.onApplicationEvent(new ApplicationPreparedEvent(new SpringApplication(), - new String[0], context)); - AutoConfigurationReportLoggingInitializer initializer = new AutoConfigurationReportLoggingInitializer(); - initializer.initialize(context); - context.refresh(); - return context; - } - @Configuration @TestAutoConfigurationPackage(City.class) protected static class EntityConfiguration {