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 0ca2a09131..81d1a2217f 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,7 +16,6 @@ package org.springframework.boot.autoconfigure.security; -import java.lang.reflect.Field; import java.util.UUID; import org.apache.commons.logging.Log; @@ -31,30 +30,21 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; 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.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; -import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer; +import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.util.ReflectionUtils; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; /** - * Configuration for a Spring Security in-memory {@link AuthenticationManager}. Can be - * disabled by providing a bean of type {@link AuthenticationManager}, - * {@link AuthenticationProvider} or {@link UserDetailsService}. The value provided by - * this configuration will become the "global" authentication manager (from Spring - * Security), or the parent of the global instance. Thus it acts as a fallback when no - * others are provided, is used by method security if enabled, and as a parent - * authentication manager for "local" authentication managers in individual filter chains. + * Configuration for a Spring Security in-memory {@link AuthenticationManager}. Adds an + * {@link InMemoryUserDetailsManager} with a default user and generated password. + * This can be disabled by providing a bean of type {@link AuthenticationManager}, + * {@link AuthenticationProvider} or {@link UserDetailsService}. * * @author Dave Syer * @author Rob Winch @@ -62,8 +52,6 @@ import org.springframework.util.ReflectionUtils; */ @Configuration @ConditionalOnBean(ObjectPostProcessor.class) -@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, - UserDetailsService.class }) @Order(0) public class AuthenticationManagerConfiguration { @@ -71,15 +59,13 @@ public class AuthenticationManagerConfiguration { .getLog(AuthenticationManagerConfiguration.class); @Bean - @Primary - public AuthenticationManager authenticationManager( - AuthenticationConfiguration configuration) throws Exception { - return configuration.getAuthenticationManager(); - } - - @Bean - public static SpringBootAuthenticationConfigurerAdapter springBootAuthenticationConfigurerAdapter() { - return new SpringBootAuthenticationConfigurerAdapter(); + @ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, + UserDetailsService.class }) + public InMemoryUserDetailsManager inMemoryUserDetailsManager() throws Exception { + String password = UUID.randomUUID().toString(); + logger.info( + String.format("%n%nUsing default security password: %s%n", password)); + return new InMemoryUserDetailsManager(User.withUsername("user").password(password).roles().build()); } @Bean @@ -87,91 +73,6 @@ public class AuthenticationManagerConfiguration { return new AuthenticationManagerConfigurationListener(); } - /** - * {@link GlobalAuthenticationConfigurerAdapter} to apply - * {@link DefaultInMemoryUserDetailsManagerConfigurer}. We must apply - * {@link DefaultInMemoryUserDetailsManagerConfigurer} in the init phase of the last - * {@link GlobalAuthenticationConfigurerAdapter}. The reason is that the typical flow - * is something like: - * - * - */ - @Order(Ordered.LOWEST_PRECEDENCE - 100) - private static class SpringBootAuthenticationConfigurerAdapter - extends GlobalAuthenticationConfigurerAdapter { - - @Override - public void init(AuthenticationManagerBuilder auth) throws Exception { - auth.apply(new DefaultInMemoryUserDetailsManagerConfigurer()); - } - - } - - /** - * {@link InMemoryUserDetailsManagerConfigurer} to add user details from - * {@link SecurityProperties}. This is necessary to delay adding the default user. - * - * - */ - private static class DefaultInMemoryUserDetailsManagerConfigurer - extends InMemoryUserDetailsManagerConfigurer { - - @Override - public void configure(AuthenticationManagerBuilder auth) throws Exception { - if (auth.isConfigured()) { - return; - } - String password = UUID.randomUUID().toString(); - logger.info( - String.format("%n%nUsing default security password: %s%n", password)); - withUser("user").password(password).roles(); - setField(auth, "defaultUserDetailsService", getUserDetailsService()); - super.configure(auth); - } - - private void setField(Object target, String name, Object value) { - try { - Field field = ReflectionUtils.findField(target.getClass(), name); - ReflectionUtils.makeAccessible(field); - ReflectionUtils.setField(field, target, value); - } - catch (Exception ex) { - logger.info("Could not set " + name); - } - } - - } - /** * {@link ApplicationListener} to autowire the {@link AuthenticationEventPublisher} * into the {@link AuthenticationManager}. 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 a65c55798b..da2dbee873 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 @@ -28,21 +28,17 @@ import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; /** * {@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 behaviours in a webapp - * provide a {@code @Configuration} with {@code @EnableWebSecurity}. To just add your own - * layer of application security in front of the defaults, add a {@code @Configuration} of - * type {@link WebSecurityConfigurerAdapter}. + * {@link InMemoryUserDetailsManager} with 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 (including static resources). * * @author Dave Syer * @author Andy Wilkinson + * @author Madhura Bhave */ @Configuration @ConditionalOnClass({ AuthenticationManager.class, 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 cc82f12fdf..27d9cb7b78 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,6 +16,7 @@ package org.springframework.boot.autoconfigure.security; +import java.util.Collections; import java.util.EnumSet; import javax.servlet.DispatcherType; @@ -38,29 +39,27 @@ import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.AnnotationConfigApplicationContext; 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; -import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.TestingAuthenticationProvider; import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.event.AbstractAuthenticationEvent; -import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; +import org.springframework.security.authentication.event.AuthenticationSuccessEvent; 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.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; /** * Tests for {@link SecurityAutoConfiguration}. @@ -68,6 +67,7 @@ import static org.junit.Assert.fail; * @author Dave Syer * @author Rob Winch * @author Andy Wilkinson + * @author Madhura Bhave */ public class SecurityAutoConfigurationTests { @@ -147,120 +147,78 @@ public class SecurityAutoConfigurationTests { } @Test - public void testDisableIgnoredStaticApplicationPaths() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(SecurityAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - TestPropertyValues.of("security.ignored:none").applyTo(this.context); - this.context.refresh(); - // Just the application endpoints now - assertThat(this.context.getBean(FilterChainProxy.class).getFilterChains()) - .hasSize(1); - } - - @Test - public void testAuthenticationManagerCreated() throws Exception { + public void testEventPublisherInjected() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); - this.context.register(SecurityAutoConfiguration.class, + this.context.register(TestAuthenticationManagerConfiguration.class, SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); - assertThat(this.context.getBean(AuthenticationManager.class)).isNotNull(); - } - - @Test - public void testEventPublisherInjected() throws Exception { - testAuthenticationManagerCreated(); - pingAuthenticationListener(); - } - - private void pingAuthenticationListener() { AuthenticationListener listener = new AuthenticationListener(); this.context.addApplicationListener(listener); AuthenticationManager manager = this.context.getBean(AuthenticationManager.class); - try { - manager.authenticate(new UsernamePasswordAuthenticationToken("foo", "wrong")); - fail("Expected BadCredentialsException"); - } - catch (BadCredentialsException e) { - // expected - } + manager.authenticate(new TestingAuthenticationToken("foo", "wrong")); assertThat(listener.event) - .isInstanceOf(AuthenticationFailureBadCredentialsEvent.class); + .isInstanceOf(AuthenticationSuccessEvent.class); } @Test - public void testOverrideAuthenticationManager() throws Exception { + public void testDefaultUsernamePassword() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); - this.context.register(TestAuthenticationConfiguration.class, - SecurityAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); + this.context.register(SecurityAutoConfiguration.class); this.context.refresh(); - assertThat(this.context.getBean(AuthenticationManager.class)) - .isEqualTo(this.context.getBean( - TestAuthenticationConfiguration.class).authenticationManager); + UserDetailsService manager = this.context.getBean(UserDetailsService.class); + assertThat(this.outputCapture.toString()).contains("Using default security password:"); + assertThat(manager.loadUserByUsername("user")).isNotNull(); } @Test - public void testDefaultAuthenticationManagerMakesUserDetailsAvailable() - throws Exception { + public void defaultUserNotCreatedIfAuthenticationManagerBeanPresent() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); - this.context.register(UserDetailsSecurityCustomizer.class, + this.context.register(TestAuthenticationManagerConfiguration.class, SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); - assertThat(this.context.getBean(UserDetailsSecurityCustomizer.class) - .getUserDetails().loadUserByUsername("user")).isNotNull(); - } - - @Test - public void testOverrideAuthenticationManagerAndInjectIntoSecurityFilter() - throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(TestAuthenticationConfiguration.class, - SecurityCustomizer.class, SecurityAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBean(AuthenticationManager.class)) + AuthenticationManager manager = this.context.getBean(AuthenticationManager.class); + assertThat(manager) .isEqualTo(this.context.getBean( - TestAuthenticationConfiguration.class).authenticationManager); + TestAuthenticationManagerConfiguration.class).authenticationManager); + assertThat(this.outputCapture.toString()) + .doesNotContain("Using default security password: "); + TestingAuthenticationToken token = new TestingAuthenticationToken( + "foo", "bar"); + assertThat(manager.authenticate(token)).isNotNull(); } @Test - public void testOverrideAuthenticationManagerWithBuilderAndInjectIntoSecurityFilter() - throws Exception { + public void defaultUserNotCreatedIfUserDetailsServiceBeanPresent() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); - this.context.register(AuthenticationManagerCustomizer.class, - SecurityCustomizer.class, SecurityAutoConfiguration.class, + this.context.register(TestUserDetailsServiceConfiguration.class, + SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); - UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken( - "foo", "bar", - AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); - assertThat(this.context.getBean(AuthenticationManager.class).authenticate(user)) - .isNotNull(); - pingAuthenticationListener(); + UserDetailsService userDetailsService = this.context.getBean(UserDetailsService.class); + assertThat(this.outputCapture.toString()) + .doesNotContain("Using default security password: "); + assertThat(userDetailsService.loadUserByUsername("foo")).isNotNull(); } @Test - public void testOverrideAuthenticationManagerWithBuilderAndInjectBuilderIntoSecurityFilter() - throws Exception { + public void defaultUserNotCreatedIfAuthenticationProviderBeanPresent() throws Exception { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); - this.context.register(AuthenticationManagerCustomizer.class, - WorkaroundSecurityCustomizer.class, SecurityAutoConfiguration.class, + this.context.register(TestAuthenticationProviderConfiguration.class, + SecurityAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); - UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken( - "foo", "bar", - AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); - assertThat(this.context.getBean(AuthenticationManager.class).authenticate(user)) - .isNotNull(); + AuthenticationProvider provider = this.context.getBean(AuthenticationProvider.class); + assertThat(this.outputCapture.toString()) + .doesNotContain("Using default security password: "); + TestingAuthenticationToken token = new TestingAuthenticationToken( + "foo", "bar"); + assertThat(provider.authenticate(token)).isNotNull(); } @Test @@ -279,41 +237,11 @@ public class SecurityAutoConfigurationTests { assertThat(this.context.getBean(JpaTransactionManager.class)).isNotNull(); } - @Test - public void testDefaultUsernamePassword() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(SecurityAutoConfiguration.class); - this.context.refresh(); - String password = this.outputCapture.toString() - .split("Using default security password: ")[1].split("\n")[0].trim(); - AuthenticationManager manager = this.context.getBean(AuthenticationManager.class); - UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( - "user", password); - assertThat(manager.authenticate(token)).isNotNull(); - } - - @Test - public void testCustomAuthenticationDoesNotCreateDefaultUser() throws Exception { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setServletContext(new MockServletContext()); - this.context.register(AuthenticationManagerCustomizer.class, - SecurityAutoConfiguration.class); - this.context.refresh(); - AuthenticationManager manager = this.context.getBean(AuthenticationManager.class); - assertThat(this.outputCapture.toString()) - .doesNotContain("Using default security password: "); - UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( - "foo", "bar"); - assertThat(manager.authenticate(token)).isNotNull(); - } - @Test public void testSecurityEvaluationContextExtensionSupport() { this.context = new AnnotationConfigWebApplicationContext(); this.context.setServletContext(new MockServletContext()); - this.context.register(AuthenticationManagerCustomizer.class, - SecurityAutoConfiguration.class); + this.context.register(SecurityAutoConfiguration.class); this.context.refresh(); assertThat(this.context.getBean(SecurityEvaluationContextExtension.class)) .isNotNull(); @@ -376,76 +304,35 @@ public class SecurityAutoConfigurationTests { } @Configuration - protected static class TestAuthenticationConfiguration { + protected static class TestAuthenticationManagerConfiguration { private AuthenticationManager authenticationManager; @Bean public AuthenticationManager myAuthenticationManager() { - this.authenticationManager = ( - authentication) -> new TestingAuthenticationToken("foo", "bar"); + AuthenticationProvider authenticationProvider = new TestingAuthenticationProvider(); + this.authenticationManager = new ProviderManager(Collections.singletonList(authenticationProvider)); return this.authenticationManager; } } @Configuration - protected static class SecurityCustomizer extends WebSecurityConfigurerAdapter { - - final AuthenticationManager authenticationManager; - - protected SecurityCustomizer(AuthenticationManager authenticationManager) { - this.authenticationManager = authenticationManager; - } - - } - - @Configuration - protected static class WorkaroundSecurityCustomizer - extends WebSecurityConfigurerAdapter { - - private final AuthenticationManagerBuilder builder; - - @SuppressWarnings("unused") - private AuthenticationManager authenticationManager; - - protected WorkaroundSecurityCustomizer(AuthenticationManagerBuilder builder) { - this.builder = builder; - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - this.authenticationManager = (authentication) -> this.builder.getOrBuild() - .authenticate(authentication); - } - - } - - @Configuration - @Order(-1) - protected static class AuthenticationManagerCustomizer - extends GlobalAuthenticationConfigurerAdapter { + protected static class TestUserDetailsServiceConfiguration { - @Override - public void init(AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication().withUser("foo").password("bar").roles("USER"); + @Bean + public InMemoryUserDetailsManager myUserDetailsService() { + return new InMemoryUserDetailsManager(User.withUsername("foo").password("bar").roles("USER").build()); } } @Configuration - protected static class UserDetailsSecurityCustomizer - extends WebSecurityConfigurerAdapter { - - private UserDetailsService userDetails; - - @Override - protected void configure(HttpSecurity http) throws Exception { - this.userDetails = http.getSharedObject(UserDetailsService.class); - } + protected static class TestAuthenticationProviderConfiguration { - public UserDetailsService getUserDetails() { - return this.userDetails; + @Bean + public AuthenticationProvider myauthenticationProvider() { + return new TestingAuthenticationProvider(); } }