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 1e9fb7465a..c764cf58bb 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 @@ -145,8 +145,9 @@ public class ManagementSecurityAutoConfigurationTests { PropertyPlaceholderAutoConfiguration.class); EnvironmentTestUtils.addEnvironment(this.context, "security.basic.enabled:false"); this.context.refresh(); - // Just the management endpoints (one filter) and ignores now - assertEquals(7, this.context.getBean(FilterChainProxy.class).getFilterChains() + // Just the management endpoints (one filter) and ignores now plus the backup + // filter on app endpoints + assertEquals(8, this.context.getBean(FilterChainProxy.class).getFilterChains() .size()); } 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 4a0f3121c7..918d5b3583 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 @@ -34,8 +34,10 @@ 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.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; @@ -66,6 +68,9 @@ public class AuthenticationManagerConfiguration extends @Autowired private SecurityProperties security; + @Autowired + private AuthenticationEventPublisher authenticationEventPublisher; + private BootDefaultingAuthenticationConfigurerAdapter configurer = new BootDefaultingAuthenticationConfigurerAdapter(); @Override @@ -84,7 +89,13 @@ public class AuthenticationManagerConfiguration extends @Lazy @Scope(proxyMode = ScopedProxyMode.INTERFACES) protected AuthenticationManager lazyAuthenticationManager() { - return this.configurer.getAuthenticationManagerBuilder().getOrBuild(); + AuthenticationManager manager = this.configurer.getAuthenticationManagerBuilder() + .getOrBuild(); + if (manager instanceof ProviderManager) { + ((ProviderManager) manager) + .setAuthenticationEventPublisher(this.authenticationEventPublisher); + } + return manager; } /** 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 288627d3d3..05db4b8cc4 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 @@ -20,10 +20,13 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** @@ -46,6 +49,13 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur AuthenticationManagerConfiguration.class }) public class SecurityAutoConfiguration { + @Bean + @ConditionalOnMissingBean + public AuthenticationEventPublisher authenticationEventPublisher( + ApplicationEventPublisher publisher) { + return new DefaultAuthenticationEventPublisher(publisher); + } + @Bean @ConditionalOnMissingBean public SecurityProperties securityProperties() { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java index a96a47d31a..2e3e60b7cf 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java @@ -39,7 +39,7 @@ public class SecurityProperties implements SecurityPrequisite { * useful place to put user-defined access rules if you want to override the default * access rules. */ - public static final int ACCESS_OVERRIDE_ORDER = SecurityProperties.BASIC_AUTH_ORDER - 1; + public static final int ACCESS_OVERRIDE_ORDER = SecurityProperties.BASIC_AUTH_ORDER - 2; /** * Order applied to the WebSecurityConfigurerAdapter that is used to configure basic diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java index 246d526bd9..b480d01fe0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SpringBootWebSecurityConfiguration.java @@ -30,14 +30,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; -import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; -import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.WebSecurityConfigurer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; @@ -89,13 +85,6 @@ public class SpringBootWebSecurityConfiguration { private static List DEFAULT_IGNORED = Arrays.asList("/css/**", "/js/**", "/images/**", "/**/favicon.ico"); - @Bean - @ConditionalOnMissingBean - public AuthenticationEventPublisher authenticationEventPublisher( - ApplicationEventPublisher publisher) { - return new DefaultAuthenticationEventPublisher(publisher); - } - @Bean @ConditionalOnMissingBean({ IgnoredPathsWebSecurityConfigurerAdapter.class }) public WebSecurityConfigurer ignoredPathsWebSecurityConfigurerAdapter() { @@ -164,7 +153,6 @@ public class SpringBootWebSecurityConfiguration { // RequestDataValueProcessor @ConditionalOnClass(RequestDataValueProcessor.class) @ConditionalOnMissingBean(RequestDataValueProcessor.class) - @ConditionalOnExpression("${security.basic.enabled:true}") @Configuration protected static class WebMvcSecurityConfigurationConditions { @@ -179,25 +167,22 @@ public class SpringBootWebSecurityConfiguration { // Pull in a plain @EnableWebSecurity if Spring MVC is not available @ConditionalOnMissingBean(WebMvcSecurityConfigurationConditions.class) @ConditionalOnMissingClass(name = "org.springframework.web.servlet.support.RequestDataValueProcessor") - @ConditionalOnExpression("${security.basic.enabled:true}") @Configuration @EnableWebSecurity protected static class DefaultWebSecurityConfiguration { } - @ConditionalOnExpression("${security.basic.enabled:true}") - @Configuration - @Order(SecurityProperties.BASIC_AUTH_ORDER) - protected static class ApplicationWebSecurityConfigurerAdapter extends + /** + * Basic functionality for all web apps (whether or not we are providing basic auth). + * @author Dave Syer + */ + private static class BaseApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Autowired private SecurityProperties security; - @Autowired - private AuthenticationEventPublisher authenticationEventPublisher; - @Override protected void configure(HttpSecurity http) throws Exception { @@ -205,17 +190,6 @@ public class SpringBootWebSecurityConfiguration { http.requiresChannel().anyRequest().requiresSecure(); } - String[] paths = getSecureApplicationPaths(); - if (this.security.getBasic().isEnabled() && paths.length > 0) { - http.exceptionHandling().authenticationEntryPoint(entryPoint()); - http.requestMatchers().antMatchers(paths); - http.authorizeRequests() - .anyRequest() - .hasAnyRole( - this.security.getUser().getRole().toArray(new String[0])) // - .and().httpBasic() // - .and().anonymous().disable(); - } if (!this.security.isEnableCsrf()) { http.csrf().disable(); } @@ -225,6 +199,9 @@ public class SpringBootWebSecurityConfiguration { SpringBootWebSecurityConfiguration.configureHeaders(http.headers(), this.security.getHeaders()); + String[] paths = getSecureApplicationPaths(); + configureAdditionalRules(http, paths); + } private String[] getSecureApplicationPaths() { @@ -241,22 +218,62 @@ public class SpringBootWebSecurityConfiguration { return list.toArray(new String[list.size()]); } + protected void configureAdditionalRules(HttpSecurity http, String... paths) + throws Exception { + } + + } + + @ConditionalOnExpression("!${security.basic.enabled:true}") + @Configuration + @Order(SecurityProperties.BASIC_AUTH_ORDER) + protected static class ApplicationNoWebSecurityConfigurerAdapter extends + BaseApplicationWebSecurityConfigurerAdapter { + @Override + protected void configureAdditionalRules(HttpSecurity http, String... paths) + throws Exception { + + if (paths.length > 0) { + http.requestMatchers().antMatchers(paths); + // The basic security was disabled + http.authorizeRequests().anyRequest().permitAll(); + } + + } + + } + + @ConditionalOnExpression("${security.basic.enabled:true}") + @Configuration + @Order(SecurityProperties.BASIC_AUTH_ORDER) + protected static class ApplicationWebSecurityConfigurerAdapter extends + BaseApplicationWebSecurityConfigurerAdapter { + + @Autowired + private SecurityProperties security; + + @Override + protected void configureAdditionalRules(HttpSecurity http, String... paths) + throws Exception { + + if (paths.length > 0) { + http.exceptionHandling().authenticationEntryPoint(entryPoint()); + http.httpBasic(); + http.requestMatchers().antMatchers(paths); + http.authorizeRequests() + .anyRequest() + .hasAnyRole( + this.security.getUser().getRole().toArray(new String[0])); + } + + } + private AuthenticationEntryPoint entryPoint() { BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); entryPoint.setRealmName(this.security.getBasic().getRealm()); return entryPoint; } - @Override - protected AuthenticationManager authenticationManager() throws Exception { - AuthenticationManager manager = super.authenticationManager(); - if (manager instanceof ProviderManager) { - ((ProviderManager) manager) - .setAuthenticationEventPublisher(this.authenticationEventPublisher); - } - return manager; - } - } } 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 7cdfd469ae..1daca280d2 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 @@ -89,8 +89,8 @@ public class SecurityAutoConfigurationTests { PropertyPlaceholderAutoConfiguration.class); EnvironmentTestUtils.addEnvironment(this.context, "security.basic.enabled:false"); this.context.refresh(); - // No security at all not even ignores - assertEquals(0, this.context.getBeanNamesForType(FilterChainProxy.class).length); + // Ignores and permitAll() security on application endpoints + assertEquals(1, this.context.getBeanNamesForType(FilterChainProxy.class).length); } @Test diff --git a/spring-boot-cli/samples/reactor.groovy b/spring-boot-cli/samples/reactor.groovy index a2343b22a2..679ffef749 100644 --- a/spring-boot-cli/samples/reactor.groovy +++ b/spring-boot-cli/samples/reactor.groovy @@ -1,9 +1,8 @@ package org.test -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CountDownLatch @EnableReactor -@Consumer @Log class Runner implements CommandLineRunner { @@ -23,9 +22,27 @@ class Runner implements CommandLineRunner { latch.await() } + @Bean + CountDownLatch latch() { + latch + } + +} + +@Consumer +@Log +class Greeter { + + @Autowired + Reactor reactor + + @Autowired + private CountDownLatch latch + @Selector(value="hello") void receive(String data) { log.info "Hello ${data}" latch.countDown() } -} + +} \ No newline at end of file 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 a221e937db..b4e9010874 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,6 +19,7 @@ package sample.ui.secure; import java.util.Date; import java.util.Map; +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; @@ -36,7 +37,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter @ComponentScan @Controller public class SampleWebSecureApplication extends WebMvcConfigurerAdapter { - + @RequestMapping("/") public String home(Map model) { model.put("message", "Hello World"); @@ -51,9 +52,7 @@ public class SampleWebSecureApplication extends WebMvcConfigurerAdapter { } public static void main(String[] args) throws Exception { - // Set user password to "password" for demo purposes only - new SpringApplicationBuilder(SampleWebSecureApplication.class).properties( - "security.user.password=password").run(args); + new SpringApplicationBuilder(SampleWebSecureApplication.class).run(args); } @Override @@ -68,8 +67,16 @@ public class SampleWebSecureApplication extends WebMvcConfigurerAdapter { @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter { + + @Autowired + private SecurityProperties security; + @Override protected void configure(HttpSecurity http) throws Exception { + if (!security.isEnableCsrf()) { + // For testing + http.csrf().disable(); + } http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin() .loginPage("/login").failureUrl("/login?error").permitAll(); } diff --git a/spring-boot-samples/spring-boot-sample-web-secure/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-web-secure/src/main/resources/application.properties index 12d9154ea9..521ecc4286 100644 --- a/spring-boot-samples/spring-boot-sample-web-secure/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-web-secure/src/main/resources/application.properties @@ -1,3 +1,5 @@ spring.thymeleaf.cache: false debug: true -security.basic.enabled: false \ No newline at end of file +security.basic.enabled: false +# demo only: +security.user.password: password \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-web-secure/src/main/resources/templates/login.html b/spring-boot-samples/spring-boot-sample-web-secure/src/main/resources/templates/login.html index 6ca32096c0..c32ff1e094 100644 --- a/spring-boot-samples/spring-boot-sample-web-secure/src/main/resources/templates/login.html +++ b/spring-boot-samples/spring-boot-sample-web-secure/src/main/resources/templates/login.html @@ -27,7 +27,7 @@ + th:name="${_csrf.parameterName}" th:value="${_csrf.token}" th:if="${_csrf}"/> diff --git a/spring-boot-samples/spring-boot-sample-web-secure/src/test/java/sample/ui/secure/SampleSecureApplicationTests.java b/spring-boot-samples/spring-boot-sample-web-secure/src/test/java/sample/ui/secure/SampleSecureApplicationTests.java index 015010380c..049bf58895 100644 --- a/spring-boot-samples/spring-boot-sample-web-secure/src/test/java/sample/ui/secure/SampleSecureApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-web-secure/src/test/java/sample/ui/secure/SampleSecureApplicationTests.java @@ -16,6 +16,10 @@ package sample.ui.secure; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import java.util.Arrays; import org.junit.Test; @@ -33,9 +37,8 @@ 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 static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; /** * Basic integration tests for demo application. @@ -45,7 +48,7 @@ import static org.junit.Assert.assertTrue; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = SampleWebSecureApplication.class) @WebAppConfiguration -@IntegrationTest("server.port:0") +@IntegrationTest({ "server.port:0", "security.enable_csrf:false" }) @DirtiesContext public class SampleSecureApplicationTests { @@ -60,8 +63,27 @@ public class SampleSecureApplicationTests { "http://localhost:" + this.port, HttpMethod.GET, new HttpEntity( headers), String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); - assertTrue("Wrong body (title doesn't match):\n" + entity.getBody(), entity - .getBody().contains("Login")); + assertTrue("Wrong body (title doesn't match):\n" + entity.getBody(), + entity.getBody().contains("<title>Login")); + } + + @Test + public void testLogin() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>(); + form.set("username", "user"); + form.set("password", "password"); + ResponseEntity<String> entity = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/login", HttpMethod.POST, + new HttpEntity<MultiValueMap<String, String>>(form, headers), + String.class); + assertEquals(HttpStatus.FOUND, entity.getStatusCode()); + assertTrue("Wrong location:\n" + entity.getHeaders(), + entity.getHeaders().getLocation().toString().endsWith(port + "/")); + assertNotNull("Missing cookie:\n" + entity.getHeaders(), + entity.getHeaders().get("Set-Cookie")); } @Test