diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/SecurityAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/SecurityAutoConfiguration.java index b2ee7f02220..e44b41bf88d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/SecurityAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/SecurityAutoConfiguration.java @@ -17,7 +17,10 @@ package org.springframework.boot.actuate.autoconfigure; import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -25,8 +28,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; import org.springframework.boot.actuate.properties.ManagementServerProperties; -import org.springframework.boot.actuate.properties.ManagementServerProperties.User; import org.springframework.boot.actuate.properties.SecurityProperties; +import org.springframework.boot.actuate.properties.SecurityProperties.User; import org.springframework.boot.actuate.web.ErrorController; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -40,13 +43,14 @@ 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.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.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; @@ -85,6 +89,7 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationEn @Configuration @ConditionalOnClass({ EnableWebSecurity.class }) @EnableWebSecurity +// (debug = true) @EnableConfigurationProperties public class SecurityAutoConfiguration { @@ -101,27 +106,25 @@ public class SecurityAutoConfiguration { } @Bean - @ConditionalOnMissingBean({ BoostrapWebSecurityConfigurerAdapter.class }) - public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() { - return new BoostrapWebSecurityConfigurerAdapter(); + @ConditionalOnMissingBean({ ApplicationWebSecurityConfigurerAdapter.class }) + public WebSecurityConfigurerAdapter applicationWebSecurityConfigurerAdapter() { + return new ApplicationWebSecurityConfigurerAdapter(); + } + + @Bean + @ConditionalOnMissingBean({ ManagementWebSecurityConfigurerAdapter.class }) + public WebSecurityConfigurerAdapter managementWebSecurityConfigurerAdapter() { + return new ManagementWebSecurityConfigurerAdapter(); } // Give user-supplied filters a chance to be last in line - @Order(Ordered.LOWEST_PRECEDENCE - 10) - private static class BoostrapWebSecurityConfigurerAdapter extends + @Order(Ordered.LOWEST_PRECEDENCE - 5) + private static class ApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { - private static final String[] NO_PATHS = new String[0]; - @Autowired private SecurityProperties security; - @Autowired - private ManagementServerProperties management; - - @Autowired(required = false) - private EndpointHandlerMapping endpointHandlerMapping; - @Autowired private AuthenticationEventPublisher authenticationEventPublisher; @@ -135,26 +138,20 @@ public class SecurityAutoConfiguration { http.requiresChannel().anyRequest().requiresSecure(); } - if (this.security.getBasic().isEnabled()) { + String[] paths = getSecureApplicationPaths(); + if (this.security.getBasic().isEnabled() && paths.length > 0) { http.exceptionHandling().authenticationEntryPoint(entryPoint()); - http.httpBasic().and().anonymous().disable(); - ExpressionUrlAuthorizationConfigurer authorizeUrls = http - .authorizeUrls(); - String[] paths = getEndpointPaths(true); - if (paths.length > 0) { - authorizeUrls.antMatchers(getEndpointPaths(true)).hasRole( - this.management.getUser().getRole()); - } - paths = getSecureApplicationPaths(); - if (paths.length > 0) { - authorizeUrls.antMatchers(getSecureApplicationPaths()).hasRole( - this.security.getBasic().getRole()); - } - authorizeUrls.and().httpBasic(); + http.requestMatchers().antMatchers(paths); + http.authorizeRequests().anyRequest() + .hasRole(this.security.getUser().getRole()) // + .and().httpBasic() // + .and().anonymous().disable(); } - - // No cookies for service endpoints by default + // Remove this when session creation is disabled by default + http.csrf().disable(); + // No cookies for application endpoints by default http.sessionManagement().sessionCreationPolicy(this.security.getSessions()); + } private String[] getSecureApplicationPaths() { @@ -181,12 +178,74 @@ public class SecurityAutoConfiguration { public void configure(WebSecurity builder) throws Exception { IgnoredRequestConfigurer ignoring = builder.ignoring(); ignoring.antMatchers(this.security.getIgnored()); - ignoring.antMatchers(getEndpointPaths(false)); if (this.errorController != null) { ignoring.antMatchers(this.errorController.getErrorPath()); } } + @Override + protected AuthenticationManager authenticationManager() throws Exception { + AuthenticationManager manager = super.authenticationManager(); + if (manager instanceof ProviderManager) { + ((ProviderManager) manager) + .setAuthenticationEventPublisher(this.authenticationEventPublisher); + } + return manager; + } + + } + + // Give user-supplied filters a chance to be last in line + @Order(Ordered.LOWEST_PRECEDENCE - 10) + private static class ManagementWebSecurityConfigurerAdapter extends + WebSecurityConfigurerAdapter { + + private static final String[] NO_PATHS = new String[0]; + + @Autowired + private SecurityProperties security; + + @Autowired + private ManagementServerProperties management; + + @Autowired(required = false) + private EndpointHandlerMapping endpointHandlerMapping; + + @Override + protected void configure(HttpSecurity http) throws Exception { + + if (this.security.isRequireSsl()) { + http.requiresChannel().anyRequest().requiresSecure(); + } + + String[] paths = getEndpointPaths(true); + if (this.security.getBasic().isEnabled() && paths.length > 0) { + http.exceptionHandling().authenticationEntryPoint(entryPoint()); + http.requestMatchers().antMatchers(paths); + http.authorizeRequests().anyRequest() + .hasRole(this.security.getManagement().getRole()) // + .and().httpBasic() // + .and().anonymous().disable(); + } + // No cookies for management endpoints by default + http.csrf().disable(); + http.sessionManagement().sessionCreationPolicy( + this.security.getManagement().getSessions()); + + } + + @Override + public void configure(WebSecurity builder) throws Exception { + IgnoredRequestConfigurer ignoring = builder.ignoring(); + ignoring.antMatchers(getEndpointPaths(false)); + } + + private AuthenticationEntryPoint entryPoint() { + BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); + entryPoint.setRealmName(this.security.getBasic().getRealm()); + return entryPoint; + } + private String[] getEndpointPaths(boolean secure) { if (this.endpointHandlerMapping == null) { return NO_PATHS; @@ -202,16 +261,6 @@ public class SecurityAutoConfiguration { return paths.toArray(new String[paths.size()]); } - @Override - protected AuthenticationManager authenticationManager() throws Exception { - AuthenticationManager manager = super.authenticationManager(); - if (manager instanceof ProviderManager) { - ((ProviderManager) manager) - .setAuthenticationEventPublisher(this.authenticationEventPublisher); - } - return manager; - } - } @ConditionalOnMissingBean(AuthenticationManager.class) @@ -222,23 +271,28 @@ public class SecurityAutoConfiguration { .getLog(AuthenticationManagerConfiguration.class); @Autowired - private ManagementServerProperties management; + private SecurityProperties security; @Bean public AuthenticationManager authenticationManager() throws Exception { - User user = this.management.getUser(); + + InMemoryUserDetailsManagerConfigurer builder = new AuthenticationManagerBuilder( + ObjectPostProcessor.QUIESCENT_POSTPROCESSOR).inMemoryAuthentication(); + User user = this.security.getUser(); + if (user.isDefaultPassword()) { - logger.info("Using default password for management endpoints: " + logger.info("Using default password for application endpoints: " + user.getPassword()); } - List roles = new ArrayList(); - roles.add("USER"); - if (!"USER".equals(user.getRole())) { - roles.add(user.getRole()); - } - return new AuthenticationManagerBuilder().inMemoryAuthentication() - .withUser(user.getName()).password(user.getPassword()) - .roles(roles.toArray(new String[roles.size()])).and().and().build(); + + Set roles = new LinkedHashSet(Arrays.asList(this.security + .getManagement().getRole(), user.getRole())); + + builder.withUser(user.getName()).password(user.getPassword()) + .roles(roles.toArray(new String[roles.size()])); + + return builder.and().build(); + } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/ManagementServerProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/ManagementServerProperties.java index 65bb156ddf9..cfaf790f21f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/ManagementServerProperties.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/ManagementServerProperties.java @@ -17,7 +17,6 @@ package org.springframework.boot.actuate.properties; import java.net.InetAddress; -import java.util.UUID; import javax.validation.constraints.NotNull; @@ -40,14 +39,8 @@ public class ManagementServerProperties { @NotNull private String contextPath = ""; - private User user = new User(); - private boolean allowShutdown = false; - public User getUser() { - return this.user; - } - public boolean isAllowShutdown() { return this.allowShutdown; } @@ -89,45 +82,4 @@ public class ManagementServerProperties { this.contextPath = contextPath; } - public static class User { - - private String name = "user"; - - private String password = UUID.randomUUID().toString(); - - private String role = "ADMIN"; - - private boolean defaultPassword; - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public String getPassword() { - return this.password; - } - - public void setPassword(String password) { - this.defaultPassword = false; - this.password = password; - } - - public String getRole() { - return this.role; - } - - public void setRole(String role) { - this.role = role; - } - - public boolean isDefaultPassword() { - return this.defaultPassword; - } - - } - } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/SecurityProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/SecurityProperties.java index efea4de379e..d216c99affb 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/SecurityProperties.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/SecurityProperties.java @@ -16,8 +16,10 @@ package org.springframework.boot.actuate.properties; +import java.util.UUID; + import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.security.config.annotation.web.configurers.SessionCreationPolicy; +import org.springframework.security.config.http.SessionCreationPolicy; /** * Properties for the security aspects of an application. @@ -31,11 +33,23 @@ public class SecurityProperties { private Basic basic = new Basic(); - private SessionCreationPolicy sessions = SessionCreationPolicy.stateless; + private SessionCreationPolicy sessions = SessionCreationPolicy.STATELESS; private String[] ignored = new String[] { "/css/**", "/js/**", "/images/**", "/**/favicon.ico" }; + private Management management = new Management(); + + private User user = new User(); + + public User getUser() { + return this.user; + } + + public Management getManagement() { + return this.management; + } + public SessionCreationPolicy getSessions() { return this.sessions; } @@ -76,8 +90,6 @@ public class SecurityProperties { private String[] path = new String[] { "/**" }; - private String role = "USER"; - public boolean isEnabled() { return this.enabled; } @@ -102,6 +114,59 @@ public class SecurityProperties { this.path = paths; } + } + + public static class Management { + + private String role = "ADMIN"; + + private SessionCreationPolicy sessions = SessionCreationPolicy.STATELESS; + + public SessionCreationPolicy getSessions() { + return this.sessions; + } + + public void setSessions(SessionCreationPolicy sessions) { + this.sessions = sessions; + } + + public void setRole(String role) { + this.role = role; + } + + public String getRole() { + return this.role; + } + + } + + public static class User { + + private String name = "user"; + + private String password = UUID.randomUUID().toString(); + + private String role = "USER"; + + private boolean defaultPassword; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.defaultPassword = false; + this.password = password; + } + public String getRole() { return this.role; } @@ -110,6 +175,10 @@ public class SecurityProperties { this.role = role; } + public boolean isDefaultPassword() { + return this.defaultPassword; + } + } } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index ee7a9e4ee06..44e6d34c92d 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -8,7 +8,7 @@ pom 4.0.0.BUILD-SNAPSHOT - 3.2.0.M2 + 3.2.0.RC1 2.2.4.RELEASE 2.2.0.RELEASE 2.1.6 diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/main/resources/logback.xml b/spring-boot-samples/spring-boot-sample-actuator/src/main/resources/logback.xml index 2ce818c2856..62e4a205ead 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/main/resources/logback.xml +++ b/spring-boot-samples/spring-boot-sample-actuator/src/main/resources/logback.xml @@ -1,19 +1,5 @@ - - - - - - - ${CONSOLE_LOG_PATTERN} - - - - - - - + - diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/ManagementAddressSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/ManagementAddressSampleActuatorApplicationTests.java index dedbdae170c..557561f9c8f 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/ManagementAddressSampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/ManagementAddressSampleActuatorApplicationTests.java @@ -30,7 +30,7 @@ import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.springframework.boot.SpringApplication; -import org.springframework.boot.actuate.properties.ManagementServerProperties; +import org.springframework.boot.actuate.properties.SecurityProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; @@ -127,7 +127,7 @@ public class ManagementAddressSampleActuatorApplicationTests { } private String getPassword() { - return context.getBean(ManagementServerProperties.class).getUser().getPassword(); + return context.getBean(SecurityProperties.class).getUser().getPassword(); } private RestTemplate getRestTemplate() { diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/NoManagementSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/NoManagementSampleActuatorApplicationTests.java index af11652ada4..b25944caf31 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/NoManagementSampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/NoManagementSampleActuatorApplicationTests.java @@ -29,7 +29,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.boot.SpringApplication; -import org.springframework.boot.actuate.properties.ManagementServerProperties; +import org.springframework.boot.actuate.properties.SecurityProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; @@ -100,7 +100,7 @@ public class NoManagementSampleActuatorApplicationTests { } private String getPassword() { - return context.getBean(ManagementServerProperties.class).getUser().getPassword(); + return context.getBean(SecurityProperties.class).getUser().getPassword(); } private RestTemplate getRestTemplate(final String username, final String password) { diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/SampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/SampleActuatorApplicationTests.java index a1e8a26df44..aed6d030f88 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/SampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/SampleActuatorApplicationTests.java @@ -29,7 +29,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.boot.SpringApplication; -import org.springframework.boot.actuate.properties.ManagementServerProperties; +import org.springframework.boot.actuate.properties.SecurityProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; @@ -86,7 +86,8 @@ public class SampleActuatorApplicationTests { @SuppressWarnings("unchecked") Map body = entity.getBody(); assertEquals("Wrong body: " + body, "Unauthorized", body.get("error")); - assertFalse(entity.getHeaders().containsKey("Set-Cookie")); + assertFalse("Wrong headers: " + entity.getHeaders(), entity.getHeaders() + .containsKey("Set-Cookie")); } @Test @@ -168,7 +169,7 @@ public class SampleActuatorApplicationTests { } private String getPassword() { - return context.getBean(ManagementServerProperties.class).getUser().getPassword(); + return context.getBean(SecurityProperties.class).getUser().getPassword(); } private RestTemplate getRestTemplate() { diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/ShutdownSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/ShutdownSampleActuatorApplicationTests.java index f3b7f522f77..fa06dd251c8 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/ShutdownSampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/ShutdownSampleActuatorApplicationTests.java @@ -29,7 +29,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.boot.SpringApplication; -import org.springframework.boot.actuate.properties.ManagementServerProperties; +import org.springframework.boot.actuate.properties.SecurityProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; @@ -100,7 +100,7 @@ public class ShutdownSampleActuatorApplicationTests { } private String getPassword() { - return context.getBean(ManagementServerProperties.class).getUser().getPassword(); + return context.getBean(SecurityProperties.class).getUser().getPassword(); } private RestTemplate getRestTemplate(final String username, final String password) { diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/UnsecureSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/UnsecureSampleActuatorApplicationTests.java index f5e32838b74..088e9dfa6b5 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/UnsecureSampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/UnsecureSampleActuatorApplicationTests.java @@ -78,7 +78,8 @@ public class UnsecureSampleActuatorApplicationTests { @SuppressWarnings("unchecked") Map body = entity.getBody(); assertEquals("Hello Phil", body.get("message")); - assertFalse(entity.getHeaders().containsKey("Set-Cookie")); + assertFalse("Wrong headers: " + entity.getHeaders(), entity.getHeaders() + .containsKey("Set-Cookie")); } private RestTemplate getRestTemplate() {