diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java index 8ac09da81e9..17d59cc5d1c 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java @@ -202,6 +202,7 @@ public class ManagementSecurityAutoConfiguration { @Configuration @ConditionalOnMissingBean(AuthenticationManager.class) + @Order(Ordered.LOWEST_PRECEDENCE - 4) protected static class ManagementAuthenticationManagerConfiguration extends AuthenticationManagerConfiguration { } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java index 3b7b73b2777..bfd02fc9528 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java @@ -18,22 +18,20 @@ package org.springframework.boot.actuate.autoconfigure; import org.junit.After; import org.junit.Test; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringApplicationBeforeRefreshEvent; -import org.springframework.boot.autoconfigure.AutoConfigurationReportLoggingInitializer; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; -import org.springframework.boot.context.listener.LoggingApplicationListener; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.mock.web.MockServletContext; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.WebSecurityConfigurer; +import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; @@ -74,7 +72,7 @@ public class ManagementSecurityAutoConfigurationTests { ManagementServerPropertiesAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); - assertNotNull(this.context.getBean(AuthenticationManager.class)); + assertNotNull(this.context.getBean(AuthenticationManagerBuilder.class)); // 6 for static resources, one for management endpoints and one for the rest assertEquals(8, this.context.getBean(FilterChainProxy.class).getFilterChains() .size()); @@ -89,9 +87,9 @@ public class ManagementSecurityAutoConfigurationTests { HttpMessageConvertersAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, SecurityAutoConfiguration.class, - ManagementSecurityAutoConfiguration.class, + ManagementSecurityAutoConfiguration.class, UserDetailsExposed.class, PropertyPlaceholderAutoConfiguration.class); - debugRefresh(this.context); + this.context.refresh(); UserDetails user = getUser(); assertTrue(user.getAuthorities().containsAll( AuthorityUtils @@ -169,17 +167,24 @@ public class ManagementSecurityAutoConfigurationTests { this.context.getBean(AuthenticationManager.class)); } - private static AnnotationConfigWebApplicationContext debugRefresh( - AnnotationConfigWebApplicationContext context) { - EnvironmentTestUtils.addEnvironment(context, "debug:true"); - LoggingApplicationListener logging = new LoggingApplicationListener(); - logging.onApplicationEvent(new SpringApplicationBeforeRefreshEvent( - new SpringApplication(), context, new String[0])); - AutoConfigurationReportLoggingInitializer initializer = new AutoConfigurationReportLoggingInitializer(); - initializer.initialize(context); - context.refresh(); - initializer.onApplicationEvent(new ContextRefreshedEvent(context)); - return context; + @Configuration + protected static class UserDetailsExposed implements + WebSecurityConfigurer { + + @Override + public void init(WebSecurity builder) throws Exception { + } + + @Override + public void configure(WebSecurity builder) throws Exception { + } + + @Bean + public AuthenticationManager authenticationManager( + AuthenticationManagerBuilder builder) throws Exception { + return builder.getOrBuild(); + } + } @Configuration 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 a8a5f0e849b..bae272a5000 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 @@ -16,22 +16,20 @@ package org.springframework.boot.autoconfigure.security; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; 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.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer; +import org.springframework.security.config.annotation.web.WebSecurityConfigurer; +import org.springframework.security.config.annotation.web.builders.WebSecurity; /** * Configuration for a Spring Security in-memory {@link AuthenticationManager}. @@ -41,10 +39,10 @@ import org.springframework.security.config.annotation.authentication.configurers @Configuration @ConditionalOnBean(ObjectPostProcessor.class) @ConditionalOnMissingBean(AuthenticationManager.class) -public class AuthenticationManagerConfiguration { - - private static Log logger = LogFactory - .getLog(AuthenticationManagerConfiguration.class); +@ConditionalOnWebApplication +@Order(Ordered.LOWEST_PRECEDENCE - 3) +public class AuthenticationManagerConfiguration implements + WebSecurityConfigurer { @Autowired private SecurityProperties security; @@ -52,26 +50,17 @@ public class AuthenticationManagerConfiguration { @Autowired private List dependencies; - @Bean - public AuthenticationManager authenticationManager( - ObjectPostProcessor objectPostProcessor) throws Exception { + @Override + public void init(WebSecurity builder) throws Exception { + } - InMemoryUserDetailsManagerConfigurer builder = new AuthenticationManagerBuilder( - objectPostProcessor).inMemoryAuthentication(); - User user = this.security.getUser(); - - if (user.isDefaultPassword()) { - logger.info("\n\nUsing default password for application endpoints: " - + user.getPassword() + "\n\n"); - } - - Set roles = new LinkedHashSet(user.getRole()); - - builder.withUser(user.getName()).password(user.getPassword()) - .roles(roles.toArray(new String[roles.size()])); - - return builder.and().build(); + @Override + public void configure(WebSecurity builder) throws Exception { + } + @Autowired + public void authentication(AuthenticationManagerBuilder builder) throws Exception { + SecurityAutoConfiguration.authentication(builder, this.security); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java index 254ef2427e5..db9d3dabe0f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java @@ -16,17 +16,39 @@ package org.springframework.boot.autoconfigure.security; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.security.SecurityProperties.User; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder; +import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.util.ReflectionUtils; /** - * {@link EnableAutoConfiguration Auto-configuration} for Spring Security. + * {@link EnableAutoConfiguration Auto-configuration} for Spring Security. Provides an + * {@link AuthenticationManager} based on configuration bound to a + * {@link SecurityProperties} bean. There is one user (named "user") whose password is + * random and printed on the console at INFO level during startup. In a webapp this + * configuration also secures all web endpoints (except some well-known static resource) + * locations with HTTP basic security. To replace all the default behaviour in a webapp + * provide a @Configuration with @EnableWebSecurity. To just add + * your own layer of application security in front of the defaults, add a + * @Configuration of type {@link WebSecurityConfigurerAdapter}. * * @author Dave Syer */ @@ -37,10 +59,67 @@ import org.springframework.security.authentication.AuthenticationManager; AuthenticationManagerConfiguration.class }) public class SecurityAutoConfiguration { + private static Log logger = LogFactory.getLog(SecurityAutoConfiguration.class); + @Bean @ConditionalOnMissingBean public SecurityProperties securityProperties() { return new SecurityProperties(); } + @Bean + @ConditionalOnBean(AuthenticationManagerBuilder.class) + @ConditionalOnMissingBean + public AuthenticationManager authenticationManager( + AuthenticationManagerBuilder builder, ObjectPostProcessor processor) + throws Exception { + if (!isBuilt(builder)) { + authentication(builder, securityProperties()); + } + else if (builder.getOrBuild() == null) { + builder = new AuthenticationManagerBuilder(processor); + authentication(builder, securityProperties()); + } + return builder.getOrBuild(); + } + + /** + * Convenience method for building the default AuthenticationManager from + * SecurityProperties. + * + * @param builder the AuthenticationManagerBuilder to use + * @param security the SecurityProperties in use + */ + public static void authentication(AuthenticationManagerBuilder builder, + SecurityProperties security) throws Exception { + + if (isBuilt(builder)) { + return; + } + + User user = security.getUser(); + + if (user.isDefaultPassword()) { + logger.info("\n\nUsing default password for application endpoints: " + + user.getPassword() + "\n\n"); + } + + Set roles = new LinkedHashSet(user.getRole()); + + builder.inMemoryAuthentication().withUser(user.getName()) + .password(user.getPassword()) + .roles(roles.toArray(new String[roles.size()])); + + } + + private static boolean isBuilt(AuthenticationManagerBuilder builder) { + Method configurers = ReflectionUtils.findMethod( + AbstractConfiguredSecurityBuilder.class, "getConfigurers"); + Method unbuilt = ReflectionUtils.findMethod( + AbstractConfiguredSecurityBuilder.class, "isUnbuilt"); + ReflectionUtils.makeAccessible(configurers); + ReflectionUtils.makeAccessible(unbuilt); + return !((Collection) ReflectionUtils.invokeMethod(configurers, builder)) + .isEmpty() || !((Boolean) ReflectionUtils.invokeMethod(unbuilt, builder)); + } } 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 6b42ab88724..c9631f51702 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 @@ -34,6 +34,7 @@ import org.springframework.mock.web.MockServletContext; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.FilterChainProxy; @@ -58,7 +59,7 @@ public class SecurityAutoConfigurationTests { this.context.register(SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); debugRefresh(this.context); - assertNotNull(this.context.getBean(AuthenticationManager.class)); + assertNotNull(this.context.getBean(AuthenticationManagerBuilder.class)); // 4 for static resources and one for the rest assertEquals(5, this.context.getBean(FilterChainProxy.class).getFilterChains() .size()); diff --git a/spring-boot-samples/spring-boot-sample-secure/src/main/java/sample/secure/SampleSecureApplication.java b/spring-boot-samples/spring-boot-sample-secure/src/main/java/sample/secure/SampleSecureApplication.java index 3b3519dbec3..ae483343d2a 100644 --- a/spring-boot-samples/spring-boot-sample-secure/src/main/java/sample/secure/SampleSecureApplication.java +++ b/spring-boot-samples/spring-boot-sample-secure/src/main/java/sample/secure/SampleSecureApplication.java @@ -48,7 +48,7 @@ public class SampleSecureApplication implements CommandLineRunner { } public static void main(String[] args) throws Exception { - SpringApplication.run(SampleSecureApplication.class, args); + SpringApplication.run(SampleSecureApplication.class, "--debug"); } } diff --git a/spring-boot-samples/spring-boot-sample-secure/src/test/java/sample/secure/SampleSecureApplicationTests.java b/spring-boot-samples/spring-boot-sample-secure/src/test/java/sample/secure/SampleSecureApplicationTests.java index e029b91fa11..142bb128c3a 100644 --- a/spring-boot-samples/spring-boot-sample-secure/src/test/java/sample/secure/SampleSecureApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-secure/src/test/java/sample/secure/SampleSecureApplicationTests.java @@ -16,12 +16,15 @@ package sample.secure; +import static org.junit.Assert.assertEquals; + import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.context.annotation.Bean; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.security.access.AccessDeniedException; @@ -34,8 +37,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import sample.secure.SampleSecureApplicationTests.TestConfiguration; -import static org.junit.Assert.assertEquals; - /** * Basic integration tests for demo application. * @@ -50,8 +51,17 @@ public class SampleSecureApplicationTests { private SampleService service; @Autowired + private ApplicationContext context; + private Authentication authentication; + @Before + public void init() { + AuthenticationManager authenticationManager = context.getBean(AuthenticationManager.class); + authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( + "user", "password")); + } + @After public void close() { SecurityContextHolder.clearContext(); @@ -84,16 +94,6 @@ public class SampleSecureApplicationTests { @Configuration protected static class TestConfiguration { - @Autowired - private AuthenticationManager authenticationManager; - - @Bean - public Authentication user() { - return authenticationManager - .authenticate(new UsernamePasswordAuthenticationToken("user", - "password")); - } - } }