diff --git a/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/SecurityConfiguration.java b/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/SecurityConfiguration.java index 2dfc06edf89..a889f633831 100644 --- a/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/SecurityConfiguration.java +++ b/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/SecurityConfiguration.java @@ -17,7 +17,7 @@ package org.springframework.bootstrap.actuate.autoconfigure; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.bootstrap.actuate.properties.EndpointsProperties; import org.springframework.bootstrap.actuate.properties.SecurityProperties; import org.springframework.bootstrap.context.annotation.ConditionalOnClass; import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; @@ -33,6 +33,8 @@ import org.springframework.security.config.annotation.web.EnableWebSecurity; import org.springframework.security.config.annotation.web.HttpConfiguration; import org.springframework.security.config.annotation.web.WebSecurityConfiguration; import org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapter; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; /** * @author Dave Syer @@ -57,30 +59,43 @@ public class SecurityConfiguration { private static class BoostrapWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { - @Value("${endpoints.health.path:/health}") - private String healthPath = "/health"; - - @Value("${endpoints.info.path:/info}") - private String infoPath = "/info"; - @Autowired private SecurityProperties security; + @Autowired + private EndpointsProperties endpoints; + @Autowired private AuthenticationEventPublisher authenticationEventPublisher; @Override protected void configure(HttpConfiguration http) throws Exception { - http.antMatcher("/**").httpBasic().and().anonymous().disable(); if (this.security.isRequireSsl()) { http.requiresChannel().antMatchers("/**").requiresSecure(); } - http.authorizeUrls().antMatchers("/**").hasRole("USER"); + if (this.security.getBasic().isEnabled()) { + http.authenticationEntryPoint(entryPoint()) + .antMatcher(this.security.getBasic().getPath()).httpBasic() + .authenticationEntryPoint(entryPoint()).and().anonymous() + .disable(); + http.authorizeUrls().antMatchers("/**") + .hasRole(this.security.getBasic().getRole()); + } + // No cookies for service endpoints by default + http.sessionManagement().sessionCreationPolicy(this.security.getSessions()); + } + + private AuthenticationEntryPoint entryPoint() { + BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); + entryPoint.setRealmName(this.security.getBasic().getRealm()); + return entryPoint; } @Override public void configure(WebSecurityConfiguration builder) throws Exception { - builder.ignoring().antMatchers(this.healthPath, this.infoPath); + builder.ignoring().antMatchers(this.endpoints.getHealth().getPath(), + this.endpoints.getInfo().getPath(), + this.endpoints.getError().getPath()); } @Override diff --git a/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/properties/SecurityProperties.java b/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/properties/SecurityProperties.java index 1b6df795476..100cdaa8477 100644 --- a/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/properties/SecurityProperties.java +++ b/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/properties/SecurityProperties.java @@ -17,9 +17,10 @@ package org.springframework.bootstrap.actuate.properties; import org.springframework.bootstrap.context.annotation.ConfigurationProperties; +import org.springframework.security.config.annotation.web.SessionCreationPolicy; /** - * Properties for the security aspects of the service. + * Properties for the security aspects of an application. * * @author Dave Syer */ @@ -28,11 +29,76 @@ public class SecurityProperties { private boolean requireSsl; + private Basic basic = new Basic(); + + private SessionCreationPolicy sessions = SessionCreationPolicy.stateless; + + public SessionCreationPolicy getSessions() { + return this.sessions; + } + + public void setSessions(SessionCreationPolicy sessions) { + this.sessions = sessions; + } + + public Basic getBasic() { + return this.basic; + } + + public void setBasic(Basic basic) { + this.basic = basic; + } + public boolean isRequireSsl() { - return requireSsl; + return this.requireSsl; } public void setRequireSsl(boolean requireSsl) { this.requireSsl = requireSsl; } + + public static class Basic { + + private boolean enabled = true; + + private String realm = "Spring"; + + private String path = "/**"; + + private String role = "USER"; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getRealm() { + return this.realm; + } + + public void setRealm(String realm) { + this.realm = realm; + } + + public String getPath() { + return this.path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getRole() { + return this.role; + } + + public void setRole(String role) { + this.role = role; + } + + } + } diff --git a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/main/resources/logging.properties b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/main/resources/logging.properties index 4e73ce1eb7f..ce0a373cc56 100644 --- a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/main/resources/logging.properties +++ b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/main/resources/logging.properties @@ -4,3 +4,4 @@ handlers = java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level = FINE sun.net.www.protocol.http.HttpURLConnection.level = ALL org.springframework.bootstrap.level = ALL +org.springframework.security.level = ALL diff --git a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/EndpointsPropertiesServiceBootstrapApplicationTests.java b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/EndpointsPropertiesServiceBootstrapApplicationTests.java similarity index 97% rename from spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/EndpointsPropertiesServiceBootstrapApplicationTests.java rename to spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/EndpointsPropertiesServiceBootstrapApplicationTests.java index 6bddff42b1c..c938ec99aaa 100644 --- a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/EndpointsPropertiesServiceBootstrapApplicationTests.java +++ b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/EndpointsPropertiesServiceBootstrapApplicationTests.java @@ -1,4 +1,4 @@ -package org.springframework.bootstrap.sample.service; +package org.springframework.bootstrap.sample.test; import java.io.IOException; import java.util.ArrayList; @@ -13,6 +13,7 @@ import org.junit.After; import org.junit.Test; import org.springframework.bootstrap.SpringApplication; import org.springframework.bootstrap.actuate.properties.EndpointsProperties; +import org.springframework.bootstrap.sample.service.ServiceBootstrapApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ManagementAddressServiceBootstrapApplicationTests.java b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ManagementAddressServiceBootstrapApplicationTests.java similarity index 97% rename from spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ManagementAddressServiceBootstrapApplicationTests.java rename to spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ManagementAddressServiceBootstrapApplicationTests.java index 7bf0deea663..4a885bc18cb 100644 --- a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ManagementAddressServiceBootstrapApplicationTests.java +++ b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ManagementAddressServiceBootstrapApplicationTests.java @@ -1,4 +1,4 @@ -package org.springframework.bootstrap.sample.service; +package org.springframework.bootstrap.sample.test; import java.io.IOException; import java.util.ArrayList; @@ -13,6 +13,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.bootstrap.SpringApplication; +import org.springframework.bootstrap.sample.service.ServiceBootstrapApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; diff --git a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ManagementServiceBootstrapApplicationTests.java b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ManagementServiceBootstrapApplicationTests.java similarity index 92% rename from spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ManagementServiceBootstrapApplicationTests.java rename to spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ManagementServiceBootstrapApplicationTests.java index c2abfbab927..5f2e538e4fd 100644 --- a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ManagementServiceBootstrapApplicationTests.java +++ b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ManagementServiceBootstrapApplicationTests.java @@ -1,4 +1,4 @@ -package org.springframework.bootstrap.sample.service; +package org.springframework.bootstrap.sample.test; import java.io.IOException; import java.util.Map; @@ -11,6 +11,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.bootstrap.SpringApplication; +import org.springframework.bootstrap.sample.service.ServiceBootstrapApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -61,7 +62,7 @@ public class ManagementServiceBootstrapApplicationTests { @SuppressWarnings("rawtypes") ResponseEntity entity = getRestTemplate().getForEntity( "http://localhost:" + port, Map.class); - assertEquals(HttpStatus.FORBIDDEN, entity.getStatusCode()); + assertEquals(HttpStatus.UNAUTHORIZED, entity.getStatusCode()); } @Test diff --git a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/NoManagementServiceBootstrapApplicationTests.java b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/NoManagementServiceBootstrapApplicationTests.java similarity index 96% rename from spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/NoManagementServiceBootstrapApplicationTests.java rename to spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/NoManagementServiceBootstrapApplicationTests.java index 773084d9c1e..81c19422050 100644 --- a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/NoManagementServiceBootstrapApplicationTests.java +++ b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/NoManagementServiceBootstrapApplicationTests.java @@ -1,4 +1,4 @@ -package org.springframework.bootstrap.sample.service; +package org.springframework.bootstrap.sample.test; import java.io.IOException; import java.util.ArrayList; @@ -13,6 +13,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.bootstrap.SpringApplication; +import org.springframework.bootstrap.sample.service.ServiceBootstrapApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; diff --git a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ServiceBootstrapApplicationTests.java b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ServiceBootstrapApplicationTests.java similarity index 92% rename from spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ServiceBootstrapApplicationTests.java rename to spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ServiceBootstrapApplicationTests.java index b759c03fadc..287becfe43f 100644 --- a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ServiceBootstrapApplicationTests.java +++ b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ServiceBootstrapApplicationTests.java @@ -1,4 +1,4 @@ -package org.springframework.bootstrap.sample.service; +package org.springframework.bootstrap.sample.test; import java.io.IOException; import java.util.ArrayList; @@ -13,6 +13,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.bootstrap.SpringApplication; +import org.springframework.bootstrap.sample.service.ServiceBootstrapApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; @@ -27,6 +28,7 @@ import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** @@ -65,11 +67,11 @@ public class ServiceBootstrapApplicationTests { @SuppressWarnings("rawtypes") ResponseEntity entity = getRestTemplate().getForEntity( "http://localhost:8080", Map.class); - // FIXME: should be UNAUTHORIZED? - assertEquals(HttpStatus.FORBIDDEN, entity.getStatusCode()); + assertEquals(HttpStatus.UNAUTHORIZED, entity.getStatusCode()); @SuppressWarnings("unchecked") Map body = entity.getBody(); - assertEquals("Wrong body: " + body, "Forbidden", body.get("error")); + assertEquals("Wrong body: " + body, "Unauthorized", body.get("error")); + assertFalse(entity.getHeaders().containsKey("Set-Cookie")); } @Test @@ -117,7 +119,7 @@ public class ServiceBootstrapApplicationTests { @Test public void testErrorPageDirectAccess() throws Exception { @SuppressWarnings("rawtypes") - ResponseEntity entity = getRestTemplate("user", "password").getForEntity( + ResponseEntity entity = getRestTemplate().getForEntity( "http://localhost:8080/error", Map.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); @SuppressWarnings("unchecked") diff --git a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ShutdownServiceBootstrapApplicationTests.java b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ShutdownServiceBootstrapApplicationTests.java similarity index 96% rename from spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ShutdownServiceBootstrapApplicationTests.java rename to spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ShutdownServiceBootstrapApplicationTests.java index 9b56b20ef5e..4b6158e17bc 100644 --- a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/service/ShutdownServiceBootstrapApplicationTests.java +++ b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/ShutdownServiceBootstrapApplicationTests.java @@ -1,4 +1,4 @@ -package org.springframework.bootstrap.sample.service; +package org.springframework.bootstrap.sample.test; import java.io.IOException; import java.util.ArrayList; @@ -13,6 +13,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.bootstrap.SpringApplication; +import org.springframework.bootstrap.sample.service.ServiceBootstrapApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; diff --git a/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/UnsecureServiceBootstrapApplicationTests.java b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/UnsecureServiceBootstrapApplicationTests.java new file mode 100644 index 00000000000..d08a0f764a4 --- /dev/null +++ b/spring-bootstrap-samples/spring-bootstrap-actuator-sample/src/test/java/org/springframework/bootstrap/sample/test/UnsecureServiceBootstrapApplicationTests.java @@ -0,0 +1,81 @@ +package org.springframework.bootstrap.sample.test; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.bootstrap.SpringApplication; +import org.springframework.bootstrap.sample.service.ServiceBootstrapApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * Integration tests for unsecured service endpoints (even with Spring Security on + * classpath). + * + * @author Dave Syer + * + */ +public class UnsecureServiceBootstrapApplicationTests { + + private static ConfigurableApplicationContext context; + + @BeforeClass + public static void start() throws Exception { + Future future = Executors + .newSingleThreadExecutor().submit( + new Callable() { + @Override + public ConfigurableApplicationContext call() throws Exception { + return (ConfigurableApplicationContext) SpringApplication + .run(ServiceBootstrapApplication.class, + "--security.basic.enabled=false"); + } + }); + context = future.get(10, TimeUnit.SECONDS); + } + + @AfterClass + public static void stop() { + if (context != null) { + context.close(); + } + } + + @Test + public void testHome() throws Exception { + @SuppressWarnings("rawtypes") + ResponseEntity entity = getRestTemplate().getForEntity( + "http://localhost:8080", Map.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + @SuppressWarnings("unchecked") + Map body = entity.getBody(); + assertEquals("Hello Phil", body.get("message")); + assertFalse(entity.getHeaders().containsKey("Set-Cookie")); + } + + private RestTemplate getRestTemplate() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { + @Override + public void handleError(ClientHttpResponse response) throws IOException { + } + }); + return restTemplate; + + } + +}