This commit is contained in:
Phillip Webb
2014-10-28 16:34:57 -07:00
parent 466ed469eb
commit d17b7c8195
19 changed files with 131 additions and 116 deletions

View File

@@ -22,7 +22,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for some health properties
*
* @author Christian Dupuis
* @since 1.2.0
*/
@ConfigurationProperties("health.status")
public class HealthIndicatorAutoConfigurationProperties {

View File

@@ -142,8 +142,8 @@ public class ManagementSecurityAutoConfiguration {
List<String> ignored = SpringBootWebSecurityConfiguration
.getIgnored(this.security);
if (!this.management.getSecurity().isEnabled()) {
ignored.addAll(Arrays.asList(getEndpointPaths(
this.endpointHandlerMapping)));
ignored.addAll(Arrays
.asList(getEndpointPaths(this.endpointHandlerMapping)));
}
if (ignored.contains("none")) {
ignored.remove("none");
@@ -227,11 +227,10 @@ public class ManagementSecurityAutoConfiguration {
http.exceptionHandling().authenticationEntryPoint(entryPoint());
paths = this.server.getPathsArray(paths);
http.requestMatchers().antMatchers(paths);
// @formatter:off
http.authorizeRequests()
.antMatchers(this.server.getPathsArray(getEndpointPaths(this.endpointHandlerMapping, false))).access("permitAll()")
String[] endpointPaths = this.server.getPathsArray(getEndpointPaths(
this.endpointHandlerMapping, false));
http.authorizeRequests().antMatchers(endpointPaths).access("permitAll()")
.anyRequest().hasRole(this.management.getSecurity().getRole());
// @formatter:on
http.httpBasic();
// No cookies for management endpoints by default

View File

@@ -36,20 +36,19 @@ public class HealthEndpoint extends AbstractEndpoint<Health> {
private final HealthIndicator healthIndicator;
private long ttl = 1000;
private long timeToLive = 1000;
/**
* Time to live for cached result. If accessed anonymously, we might need to cache the
* result of this endpoint to prevent a DOS attack.
*
* @return time to live in milliseconds (default 1000)
*/
public long getTtl() {
return ttl;
public long getTimeToLive() {
return this.timeToLive;
}
public void setTtl(long ttl) {
this.ttl = ttl;
public void setTimeToLive(long ttl) {
this.timeToLive = ttl;
}
/**

View File

@@ -18,8 +18,9 @@ package org.springframework.boot.actuate.endpoint.mvc;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@@ -52,7 +53,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
public class EndpointHandlerMapping extends RequestMappingHandlerMapping implements
ApplicationContextAware {
private final Map<String, MvcEndpoint> endpoints = new HashMap<String, MvcEndpoint>();
private final Map<String, MvcEndpoint> endpoints;
private String prefix = "";
@@ -64,15 +65,21 @@ public class EndpointHandlerMapping extends RequestMappingHandlerMapping impleme
* @param endpoints
*/
public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints) {
HashMap<String, MvcEndpoint> map = (HashMap<String, MvcEndpoint>) this.endpoints;
for (MvcEndpoint endpoint : endpoints) {
map.put(endpoint.getPath(), endpoint);
}
this.endpoints = buildEndpointsMap(endpoints);
// By default the static resource handler mapping is LOWEST_PRECEDENCE - 1
// and the RequestMappingHandlerMapping is 0 (we ideally want to be before both)
setOrder(-100);
}
private Map<String, MvcEndpoint> buildEndpointsMap(
Collection<? extends MvcEndpoint> endpoints) {
Map<String, MvcEndpoint> map = new LinkedHashMap<String, MvcEndpoint>();
for (MvcEndpoint endpoint : endpoints) {
map.put(endpoint.getPath(), endpoint);
}
return Collections.unmodifiableMap(map);
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();

View File

@@ -101,29 +101,26 @@ public class HealthMvcEndpoint implements MvcEndpoint {
@RequestMapping
@ResponseBody
public Object invoke(Principal principal) {
if (!delegate.isEnabled()) {
if (!this.delegate.isEnabled()) {
// Shouldn't happen because the request mapping should not be registered
return new ResponseEntity<Map<String, String>>(Collections.singletonMap(
"message", "This endpoint is disabled"), HttpStatus.NOT_FOUND);
}
Health health = getHealth(principal);
Status status = health.getStatus();
if (this.statusMapping.containsKey(status.getCode())) {
return new ResponseEntity<Health>(health, this.statusMapping.get(status
.getCode()));
}
return health;
}
private Health getHealth(Principal principal) {
Health health = useCachedValue(principal) ? cached : (Health) delegate.invoke();
Health health = (useCachedValue(principal) ? this.cached : (Health) this.delegate
.invoke());
// Not too worried about concurrent access here, the worst that can happen is the
// odd extra call to delegate.invoke()
cached = health;
this.cached = health;
if (!secure(principal)) {
// If not secure we only expose the status
health = Health.status(health.getStatus()).build();
@@ -137,12 +134,12 @@ public class HealthMvcEndpoint implements MvcEndpoint {
private boolean useCachedValue(Principal principal) {
long currentAccess = System.currentTimeMillis();
if (cached == null || secure(principal)
|| currentAccess - lastAccess > delegate.getTtl()) {
lastAccess = currentAccess;
if (this.cached == null || secure(principal)
|| (currentAccess - this.lastAccess) > this.delegate.getTimeToLive()) {
this.lastAccess = currentAccess;
return false;
}
return cached != null;
return this.cached != null;
}
@Override

View File

@@ -72,7 +72,7 @@ public class ConfigurationPropertiesReportEndpointTests extends
@SuppressWarnings("unchecked")
public void testKeySanitization() throws Exception {
ConfigurationPropertiesReportEndpoint report = getEndpointBean();
report.setKeysToSanitize(new String[] { "property" });
report.setKeysToSanitize("property");
Map<String, Object> properties = report.invoke();
Map<String, Object> nestedProperties = (Map<String, Object>) ((Map<String, Object>) properties
.get("testProperties")).get("properties");

View File

@@ -16,11 +16,6 @@
package org.springframework.boot.actuate.endpoint.mvc;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import java.util.Collections;
import org.junit.Before;
@@ -33,10 +28,16 @@ import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HealthMvcEndpoint}.
*
* @author Christian Dupuis
* @author Dave Syer
*/
public class HealthMvcEndpointTests {
@@ -92,7 +93,7 @@ public class HealthMvcEndpointTests {
public void secure() {
given(this.endpoint.invoke()).willReturn(
new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(user);
Object result = this.mvc.invoke(this.user);
assertTrue(result instanceof Health);
assertTrue(((Health) result).getStatus() == Status.UP);
assertEquals("bar", ((Health) result).getDetails().get("foo"));
@@ -100,25 +101,25 @@ public class HealthMvcEndpointTests {
@Test
public void secureNotCached() {
given(this.endpoint.getTtl()).willReturn(10000L);
given(this.endpoint.getTimeToLive()).willReturn(10000L);
given(this.endpoint.invoke()).willReturn(
new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(user);
Object result = this.mvc.invoke(this.user);
assertTrue(result instanceof Health);
assertTrue(((Health) result).getStatus() == Status.UP);
given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build());
result = this.mvc.invoke(user);
result = this.mvc.invoke(this.user);
@SuppressWarnings("unchecked")
Health health = (Health) ((ResponseEntity<Health>) result).getBody();
Health health = ((ResponseEntity<Health>) result).getBody();
assertTrue(health.getStatus() == Status.DOWN);
}
@Test
public void unsecureCached() {
given(this.endpoint.getTtl()).willReturn(10000L);
given(this.endpoint.getTimeToLive()).willReturn(10000L);
given(this.endpoint.invoke()).willReturn(
new Health.Builder().up().withDetail("foo", "bar").build());
Object result = this.mvc.invoke(user);
Object result = this.mvc.invoke(this.user);
assertTrue(result instanceof Health);
assertTrue(((Health) result).getStatus() == Status.UP);
given(this.endpoint.invoke()).willReturn(new Health.Builder().down().build());