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 5436f511d37..16a2e4e0ea7 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,6 +26,7 @@ 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.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -41,9 +42,15 @@ 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.stereotype.Component; /** - * Configuration for a Spring Security in-memory {@link AuthenticationManager}. + * Configuration for a Spring Security in-memory {@link AuthenticationManager}. Can be + * disabled by providing a bean of type AuthenticationManager. 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. * * @author Dave Syer * @author Rob Winch @@ -53,8 +60,15 @@ import org.springframework.security.config.annotation.authentication.configurers @ConditionalOnMissingBean(AuthenticationManager.class) @Order(Ordered.LOWEST_PRECEDENCE - 3) public class AuthenticationManagerConfiguration extends - GlobalAuthenticationConfigurerAdapter implements - ApplicationListener { + GlobalAuthenticationConfigurerAdapter { + + /* + * Yes, this class is a GlobalAuthenticationConfigurerAdapter, even though none of + * those methods are overridden: we want Spring Security to instantiate us early, so + * we can in turn force the SecurityPrequisites to be instantiated. This will prevent + * ordering issues between Spring Boot modules when they need to influence the default + * security configuration. + */ private static Log logger = LogFactory .getLog(AuthenticationManagerConfiguration.class); @@ -62,43 +76,48 @@ public class AuthenticationManagerConfiguration extends @Autowired private List dependencies; - @Autowired - private ObjectPostProcessor objectPostProcessor; - @Autowired private SecurityProperties security; @Autowired - private AuthenticationEventPublisher authenticationEventPublisher; - - private BootDefaultingAuthenticationConfigurerAdapter configurer = new BootDefaultingAuthenticationConfigurerAdapter(); - - @Override - public void init(AuthenticationManagerBuilder auth) throws Exception { - auth.apply(this.configurer); - } - - @Override - public void configure(AuthenticationManagerBuilder auth) throws Exception { - this.configurer.configureParent(auth); - } + private ObjectPostProcessor objectPostProcessor; @Bean @Primary - public AuthenticationManager authenticationManager() { - AuthenticationManager manager = this.configurer.getAuthenticationManagerBuilder() + public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth) + throws Exception { + /* + * 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; } - @Override - public void onApplicationEvent(ContextRefreshedEvent event) { - AuthenticationManager manager = this.configurer.getAuthenticationManagerBuilder() - .getOrBuild(); - if (manager instanceof ProviderManager) { - ((ProviderManager) manager) - .setAuthenticationEventPublisher(this.authenticationEventPublisher); + @Component + protected static class AuthenticationManagerConfigurationListener implements + ApplicationListener { + + @Autowired + private AuthenticationEventPublisher authenticationEventPublisher; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + ApplicationContext context = event.getApplicationContext(); + if (context.getBeanNamesForType(AuthenticationManager.class).length == 0) { + return; + } + AuthenticationManager manager = context.getBean(AuthenticationManager.class); + if (manager instanceof ProviderManager) { + ((ProviderManager) manager) + .setAuthenticationEventPublisher(this.authenticationEventPublisher); + } } + } /** @@ -163,7 +182,8 @@ public class AuthenticationManagerConfiguration extends .roles(roles.toArray(new String[roles.size()])).and().and().build(); // Defer actually setting the parent on the AuthenticationManagerBuilder - // because it makes it "configured" and we are only in the init() phase here. + // because it makes it "configured" and we are only in the init() phase + // here. } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerIntegrationTest.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerIntegrationTest.java index 65a78a9eccb..991b966b8c0 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerIntegrationTest.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerIntegrationTest.java @@ -18,15 +18,29 @@ package org.springframework.boot.autoconfigure.web; import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.web.BasicErrorControllerMockMvcTests.MinimalWebConfiguration; +import org.springframework.boot.autoconfigure.web.BasicErrorControllerMockMvcTests.TestConfiguration; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.TestRestTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.validation.BindException; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.AbstractView; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; @@ -40,8 +54,9 @@ import static org.junit.Assert.assertThat; * @author Dave Syer */ @RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = BasicErrorControllerMockMvcTests.TestConfiguration.class) +@SpringApplicationConfiguration(classes = TestConfiguration.class) @WebAppConfiguration +@DirtiesContext @IntegrationTest("server.port=0") public class BasicErrorControllerIntegrationTest { @@ -69,4 +84,49 @@ public class BasicErrorControllerIntegrationTest { assertThat(resp, containsString("errors=[{codes=")); assertThat(resp, containsString("org.springframework.validation.BindException")); } + + @Configuration + @MinimalWebConfiguration + public static class TestConfiguration { + + // For manual testing + public static void main(String[] args) { + SpringApplication.run(TestConfiguration.class, args); + } + + @Bean + public View error() { + return new AbstractView() { + @Override + protected void renderMergedOutputModel(Map model, + HttpServletRequest request, HttpServletResponse response) + throws Exception { + response.getWriter().write("ERROR_BEAN"); + } + }; + } + + @RestController + protected static class Errors { + + public String getFoo() { + return "foo"; + } + + @RequestMapping("/") + public String home() { + throw new IllegalStateException("Expected!"); + } + + @RequestMapping("/bind") + public String bind() throws Exception { + BindException error = new BindException(this, "test"); + error.rejectValue("foo", "bar.error"); + throw error; + } + + } + + } + } diff --git a/spring-boot-samples/spring-boot-sample-web-secure/pom.xml b/spring-boot-samples/spring-boot-sample-web-secure/pom.xml index 70170d6782e..9444f92294b 100644 --- a/spring-boot-samples/spring-boot-sample-web-secure/pom.xml +++ b/spring-boot-samples/spring-boot-sample-web-secure/pom.xml @@ -23,18 +23,10 @@ org.springframework.boot spring-boot-starter-security - - org.springframework.boot - spring-boot-starter-jdbc - org.springframework.boot spring-boot-starter-thymeleaf - - com.h2database - h2 - org.apache.httpcomponents httpclient diff --git a/spring-boot-samples/spring-boot-sample-web-secure/src/main/java/sample/ui/secure/SampleWebSecureApplication.java b/spring-boot-samples/spring-boot-sample-web-secure/src/main/java/sample/ui/secure/SampleWebSecureApplication.java index cec652a47db..186d3ef5c72 100644 --- a/spring-boot-samples/spring-boot-sample-web-secure/src/main/java/sample/ui/secure/SampleWebSecureApplication.java +++ b/spring-boot-samples/spring-boot-sample-web-secure/src/main/java/sample/ui/secure/SampleWebSecureApplication.java @@ -19,18 +19,14 @@ package sample.ui.secure; import java.util.Date; import java.util.Map; -import javax.sql.DataSource; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; -import org.springframework.core.Ordered; +import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; 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.stereotype.Controller; @@ -65,16 +61,7 @@ public class SampleWebSecureApplication extends WebMvcConfigurerAdapter { registry.addViewController("/login").setViewName("login"); } - @Bean - public ApplicationSecurity applicationSecurity() { - return new ApplicationSecurity(); - } - - @Bean - public AuthenticationSecurity authenticationSecurity() { - return new AuthenticationSecurity(); - } - + @Configuration @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter { @@ -86,21 +73,14 @@ public class SampleWebSecureApplication extends WebMvcConfigurerAdapter { http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin() .loginPage("/login").failureUrl("/login?error").permitAll(); } - } - - @Order(Ordered.HIGHEST_PRECEDENCE + 10) - protected static class AuthenticationSecurity extends - GlobalAuthenticationConfigurerAdapter { - - @Autowired - private DataSource dataSource; @Override - public void init(AuthenticationManagerBuilder auth) throws Exception { - auth.jdbcAuthentication().dataSource(this.dataSource).withUser("admin") - .password("admin").roles("ADMIN", "USER").and().withUser("user") - .password("user").roles("USER"); + public void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication().withUser("admin").password("admin") + .roles("ADMIN", "USER").and().withUser("user").password("user") + .roles("USER"); } + } }