Use role-based security to show details in the health endpoint
Closes gh-11869
This commit is contained in:
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.health;
|
package org.springframework.boot.actuate.autoconfigure.health;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||||
import org.springframework.boot.actuate.health.ShowDetails;
|
import org.springframework.boot.actuate.health.ShowDetails;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
@@ -29,9 +32,15 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||||||
public class HealthEndpointProperties {
|
public class HealthEndpointProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to show full health details.
|
* When to show full health details.
|
||||||
*/
|
*/
|
||||||
private ShowDetails showDetails = ShowDetails.WHEN_AUTHENTICATED;
|
private ShowDetails showDetails = ShowDetails.WHEN_AUTHORIZED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Roles used to determine whether or not a user is authorized to be shown details.
|
||||||
|
* When empty, all authenticated users are authorized.
|
||||||
|
*/
|
||||||
|
private Set<String> roles = new HashSet<>();
|
||||||
|
|
||||||
public ShowDetails getShowDetails() {
|
public ShowDetails getShowDetails() {
|
||||||
return this.showDetails;
|
return this.showDetails;
|
||||||
@@ -41,4 +50,12 @@ public class HealthEndpointProperties {
|
|||||||
this.showDetails = showDetails;
|
this.showDetails = showDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<String> getRoles() {
|
||||||
|
return this.roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoles(Set<String> roles) {
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.springframework.boot.actuate.health.HealthEndpoint;
|
|||||||
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
|
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
|
||||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||||
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
|
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
|
||||||
|
import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper;
|
||||||
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
|
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
|
||||||
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
|
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
|
||||||
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
||||||
@@ -59,6 +60,15 @@ class HealthEndpointWebExtensionConfiguration {
|
|||||||
return statusHttpMapper;
|
return statusHttpMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public HealthWebEndpointResponseMapper healthWebEndpointResponseMapper(
|
||||||
|
HealthStatusHttpMapper statusHttpMapper,
|
||||||
|
HealthEndpointProperties properties) {
|
||||||
|
return new HealthWebEndpointResponseMapper(statusHttpMapper,
|
||||||
|
properties.getShowDetails(), properties.getRoles());
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnWebApplication(type = Type.REACTIVE)
|
@ConditionalOnWebApplication(type = Type.REACTIVE)
|
||||||
static class ReactiveWebHealthConfiguration {
|
static class ReactiveWebHealthConfiguration {
|
||||||
@@ -81,10 +91,9 @@ class HealthEndpointWebExtensionConfiguration {
|
|||||||
@ConditionalOnEnabledEndpoint
|
@ConditionalOnEnabledEndpoint
|
||||||
@ConditionalOnBean(HealthEndpoint.class)
|
@ConditionalOnBean(HealthEndpoint.class)
|
||||||
public ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension(
|
public ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension(
|
||||||
HealthStatusHttpMapper healthStatusHttpMapper,
|
HealthWebEndpointResponseMapper responseMapper) {
|
||||||
HealthEndpointProperties properties) {
|
|
||||||
return new ReactiveHealthEndpointWebExtension(this.reactiveHealthIndicator,
|
return new ReactiveHealthEndpointWebExtension(this.reactiveHealthIndicator,
|
||||||
healthStatusHttpMapper, properties.getShowDetails());
|
responseMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -99,11 +108,10 @@ class HealthEndpointWebExtensionConfiguration {
|
|||||||
@ConditionalOnBean(HealthEndpoint.class)
|
@ConditionalOnBean(HealthEndpoint.class)
|
||||||
public HealthEndpointWebExtension healthEndpointWebExtension(
|
public HealthEndpointWebExtension healthEndpointWebExtension(
|
||||||
ApplicationContext applicationContext,
|
ApplicationContext applicationContext,
|
||||||
HealthStatusHttpMapper healthStatusHttpMapper,
|
HealthWebEndpointResponseMapper responseMapper) {
|
||||||
HealthEndpointProperties properties) {
|
|
||||||
return new HealthEndpointWebExtension(
|
return new HealthEndpointWebExtension(
|
||||||
HealthIndicatorBeansComposite.get(applicationContext),
|
HealthIndicatorBeansComposite.get(applicationContext),
|
||||||
healthStatusHttpMapper, properties.getShowDetails());
|
responseMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import java.util.function.Function;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||||
@@ -58,8 +59,8 @@ public class CloudFoundryWebEndpointDiscovererTests {
|
|||||||
for (ExposableWebEndpoint endpoint : endpoints) {
|
for (ExposableWebEndpoint endpoint : endpoints) {
|
||||||
if (endpoint.getId().equals("health")) {
|
if (endpoint.getId().equals("health")) {
|
||||||
WebOperation operation = endpoint.getOperations().iterator().next();
|
WebOperation operation = endpoint.getOperations().iterator().next();
|
||||||
assertThat(operation
|
assertThat(operation.invoke(new InvocationContext(
|
||||||
.invoke(new InvocationContext(null, Collections.emptyMap())))
|
mock(SecurityContext.class), Collections.emptyMap())))
|
||||||
.isEqualTo("cf");
|
.isEqualTo("cf");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,17 +17,19 @@
|
|||||||
package org.springframework.boot.actuate.autoconfigure.health;
|
package org.springframework.boot.actuate.autoconfigure.health;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
|
import org.springframework.boot.actuate.health.Health;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
|
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
|
||||||
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
|
import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,12 +65,17 @@ public class HealthEndpointWebExtensionTests {
|
|||||||
.withPropertyValues("management.health.status.http-mapping.CUSTOM=500")
|
.withPropertyValues("management.health.status.http-mapping.CUSTOM=500")
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
Object extension = context.getBean(HealthEndpointWebExtension.class);
|
Object extension = context.getBean(HealthEndpointWebExtension.class);
|
||||||
HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils
|
HealthWebEndpointResponseMapper responseMapper = (HealthWebEndpointResponseMapper) ReflectionTestUtils
|
||||||
.getField(extension, "statusHttpMapper");
|
.getField(extension, "responseMapper");
|
||||||
Map<String, Integer> statusMappings = mapper.getStatusMapping();
|
Class<SecurityContext> securityContext = SecurityContext.class;
|
||||||
assertThat(statusMappings).containsEntry("DOWN", 503);
|
assertThat(responseMapper
|
||||||
assertThat(statusMappings).containsEntry("OUT_OF_SERVICE", 503);
|
.map(Health.down().build(), mock(securityContext))
|
||||||
assertThat(statusMappings).containsEntry("CUSTOM", 500);
|
.getStatus()).isEqualTo(503);
|
||||||
|
assertThat(responseMapper.map(Health.status("OUT_OF_SERVICE").build(),
|
||||||
|
mock(securityContext)).getStatus()).isEqualTo(503);
|
||||||
|
assertThat(responseMapper
|
||||||
|
.map(Health.status("CUSTOM").build(), mock(securityContext))
|
||||||
|
.getStatus()).isEqualTo(500);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +84,8 @@ public class HealthEndpointWebExtensionTests {
|
|||||||
this.contextRunner.run((context) -> {
|
this.contextRunner.run((context) -> {
|
||||||
HealthEndpointWebExtension extension = context
|
HealthEndpointWebExtension extension = context
|
||||||
.getBean(HealthEndpointWebExtension.class);
|
.getBean(HealthEndpointWebExtension.class);
|
||||||
assertThat(extension.getHealth(null).getBody().getDetails()).isEmpty();
|
assertThat(extension.getHealth(mock(SecurityContext.class)).getBody()
|
||||||
|
.getDetails()).isEmpty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +94,9 @@ public class HealthEndpointWebExtensionTests {
|
|||||||
this.contextRunner.run((context) -> {
|
this.contextRunner.run((context) -> {
|
||||||
HealthEndpointWebExtension extension = context
|
HealthEndpointWebExtension extension = context
|
||||||
.getBean(HealthEndpointWebExtension.class);
|
.getBean(HealthEndpointWebExtension.class);
|
||||||
assertThat(extension.getHealth(mock(Principal.class)).getBody().getDetails())
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
|
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
|
||||||
|
assertThat(extension.getHealth(securityContext).getBody().getDetails())
|
||||||
.isNotEmpty();
|
.isNotEmpty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -110,9 +120,60 @@ public class HealthEndpointWebExtensionTests {
|
|||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
HealthEndpointWebExtension extension = context
|
HealthEndpointWebExtension extension = context
|
||||||
.getBean(HealthEndpointWebExtension.class);
|
.getBean(HealthEndpointWebExtension.class);
|
||||||
assertThat(extension.getHealth(mock(Principal.class)).getBody()
|
assertThat(extension.getHealth(mock(SecurityContext.class)).getBody()
|
||||||
.getDetails()).isEmpty();
|
.getDetails()).isEmpty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void detailsCanBeHiddenFromUnauthorizedUsers() {
|
||||||
|
this.contextRunner.withPropertyValues(
|
||||||
|
"management.endpoint.health.show-details=when-authorized",
|
||||||
|
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
|
||||||
|
HealthEndpointWebExtension extension = context
|
||||||
|
.getBean(HealthEndpointWebExtension.class);
|
||||||
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
|
given(securityContext.getPrincipal())
|
||||||
|
.willReturn(mock(Principal.class));
|
||||||
|
given(securityContext.isUserInRole("ACTUATOR")).willReturn(false);
|
||||||
|
assertThat(
|
||||||
|
extension.getHealth(securityContext).getBody().getDetails())
|
||||||
|
.isEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void detailsCanBeShownToAuthorizedUsers() {
|
||||||
|
this.contextRunner.withPropertyValues(
|
||||||
|
"management.endpoint.health.show-details=when-authorized",
|
||||||
|
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
|
||||||
|
HealthEndpointWebExtension extension = context
|
||||||
|
.getBean(HealthEndpointWebExtension.class);
|
||||||
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
|
given(securityContext.getPrincipal())
|
||||||
|
.willReturn(mock(Principal.class));
|
||||||
|
given(securityContext.isUserInRole("ACTUATOR")).willReturn(true);
|
||||||
|
assertThat(
|
||||||
|
extension.getHealth(securityContext).getBody().getDetails())
|
||||||
|
.isNotEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roleCanBeCustomized() {
|
||||||
|
this.contextRunner.withPropertyValues(
|
||||||
|
"management.endpoint.health.show-details=when-authorized",
|
||||||
|
"management.endpoint.health.roles=ADMIN").run((context) -> {
|
||||||
|
HealthEndpointWebExtension extension = context
|
||||||
|
.getBean(HealthEndpointWebExtension.class);
|
||||||
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
|
given(securityContext.getPrincipal())
|
||||||
|
.willReturn(mock(Principal.class));
|
||||||
|
given(securityContext.isUserInRole("ADMIN")).willReturn(true);
|
||||||
|
assertThat(
|
||||||
|
extension.getHealth(securityContext).getBody().getDetails())
|
||||||
|
.isNotEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,15 @@
|
|||||||
package org.springframework.boot.actuate.autoconfigure.health;
|
package org.springframework.boot.actuate.autoconfigure.health;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.health.Health;
|
import org.springframework.boot.actuate.health.Health;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||||
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
|
import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper;
|
||||||
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
|
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
|
||||||
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
||||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||||
@@ -34,6 +34,7 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,12 +70,17 @@ public class ReactiveHealthEndpointWebExtensionTests {
|
|||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
Object extension = context
|
Object extension = context
|
||||||
.getBean(ReactiveHealthEndpointWebExtension.class);
|
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||||
HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils
|
HealthWebEndpointResponseMapper responseMapper = (HealthWebEndpointResponseMapper) ReflectionTestUtils
|
||||||
.getField(extension, "statusHttpMapper");
|
.getField(extension, "responseMapper");
|
||||||
Map<String, Integer> statusMappings = mapper.getStatusMapping();
|
Class<SecurityContext> securityContext = SecurityContext.class;
|
||||||
assertThat(statusMappings).containsEntry("DOWN", 503);
|
assertThat(responseMapper
|
||||||
assertThat(statusMappings).containsEntry("OUT_OF_SERVICE", 503);
|
.map(Health.down().build(), mock(securityContext))
|
||||||
assertThat(statusMappings).containsEntry("CUSTOM", 500);
|
.getStatus()).isEqualTo(503);
|
||||||
|
assertThat(responseMapper.map(Health.status("OUT_OF_SERVICE").build(),
|
||||||
|
mock(securityContext)).getStatus()).isEqualTo(503);
|
||||||
|
assertThat(responseMapper
|
||||||
|
.map(Health.status("CUSTOM").build(), mock(securityContext))
|
||||||
|
.getStatus()).isEqualTo(500);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +92,11 @@ public class ReactiveHealthEndpointWebExtensionTests {
|
|||||||
ReactiveHealthEndpointWebExtension extension = context
|
ReactiveHealthEndpointWebExtension extension = context
|
||||||
.getBean(ReactiveHealthEndpointWebExtension.class);
|
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||||
Health endpointHealth = endpoint.health();
|
Health endpointHealth = endpoint.health();
|
||||||
Health extensionHealth = extension.health(mock(Principal.class))
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
.block().getBody();
|
given(securityContext.getPrincipal())
|
||||||
|
.willReturn(mock(Principal.class));
|
||||||
|
Health extensionHealth = extension.health(securityContext).block()
|
||||||
|
.getBody();
|
||||||
assertThat(endpointHealth.getDetails())
|
assertThat(endpointHealth.getDetails())
|
||||||
.containsOnlyKeys("application", "first", "second");
|
.containsOnlyKeys("application", "first", "second");
|
||||||
assertThat(extensionHealth.getDetails())
|
assertThat(extensionHealth.getDetails())
|
||||||
@@ -100,7 +109,8 @@ public class ReactiveHealthEndpointWebExtensionTests {
|
|||||||
this.contextRunner.run((context) -> {
|
this.contextRunner.run((context) -> {
|
||||||
ReactiveHealthEndpointWebExtension extension = context
|
ReactiveHealthEndpointWebExtension extension = context
|
||||||
.getBean(ReactiveHealthEndpointWebExtension.class);
|
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||||
assertThat(extension.health(null).block().getBody().getDetails()).isEmpty();
|
assertThat(extension.health(mock(SecurityContext.class)).block().getBody()
|
||||||
|
.getDetails()).isEmpty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +119,10 @@ public class ReactiveHealthEndpointWebExtensionTests {
|
|||||||
this.contextRunner.run((context) -> {
|
this.contextRunner.run((context) -> {
|
||||||
ReactiveHealthEndpointWebExtension extension = context
|
ReactiveHealthEndpointWebExtension extension = context
|
||||||
.getBean(ReactiveHealthEndpointWebExtension.class);
|
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||||
assertThat(extension.health(mock(Principal.class)).block().getBody()
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
.getDetails()).isNotEmpty();
|
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
|
||||||
|
assertThat(extension.health(securityContext).block().getBody().getDetails())
|
||||||
|
.isNotEmpty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,11 +145,60 @@ public class ReactiveHealthEndpointWebExtensionTests {
|
|||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
ReactiveHealthEndpointWebExtension extension = context
|
ReactiveHealthEndpointWebExtension extension = context
|
||||||
.getBean(ReactiveHealthEndpointWebExtension.class);
|
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||||
assertThat(extension.health(mock(Principal.class)).block().getBody()
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
|
assertThat(extension.health(securityContext).block().getBody()
|
||||||
.getDetails()).isEmpty();
|
.getDetails()).isEmpty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void detailsCanBeHiddenFromUnauthorizedUsers() {
|
||||||
|
this.contextRunner.withPropertyValues(
|
||||||
|
"management.endpoint.health.show-details=when-authorized",
|
||||||
|
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
|
||||||
|
ReactiveHealthEndpointWebExtension extension = context
|
||||||
|
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||||
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
|
given(securityContext.getPrincipal())
|
||||||
|
.willReturn(mock(Principal.class));
|
||||||
|
given(securityContext.isUserInRole("ACTUATOR")).willReturn(false);
|
||||||
|
assertThat(extension.health(securityContext).block().getBody()
|
||||||
|
.getDetails()).isEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void detailsCanBeShownToAuthorizedUsers() {
|
||||||
|
this.contextRunner.withPropertyValues(
|
||||||
|
"management.endpoint.health.show-details=when-authorized",
|
||||||
|
"management.endpoint.health.roles=ACTUATOR").run((context) -> {
|
||||||
|
ReactiveHealthEndpointWebExtension extension = context
|
||||||
|
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||||
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
|
given(securityContext.getPrincipal())
|
||||||
|
.willReturn(mock(Principal.class));
|
||||||
|
given(securityContext.isUserInRole("ACTUATOR")).willReturn(true);
|
||||||
|
assertThat(extension.health(securityContext).block().getBody()
|
||||||
|
.getDetails()).isNotEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roleCanBeCustomized() {
|
||||||
|
this.contextRunner.withPropertyValues(
|
||||||
|
"management.endpoint.health.show-details=when-authorized",
|
||||||
|
"management.endpoint.health.roles=ADMIN").run((context) -> {
|
||||||
|
ReactiveHealthEndpointWebExtension extension = context
|
||||||
|
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||||
|
SecurityContext securityContext = mock(SecurityContext.class);
|
||||||
|
given(securityContext.getPrincipal())
|
||||||
|
.willReturn(mock(Principal.class));
|
||||||
|
given(securityContext.isUserInRole("ADMIN")).willReturn(true);
|
||||||
|
assertThat(extension.health(securityContext).block().getBody()
|
||||||
|
.getDetails()).isNotEmpty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class HealthIndicatorsConfiguration {
|
static class HealthIndicatorsConfiguration {
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint;
|
package org.springframework.boot.actuate.endpoint;
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||||
@@ -30,24 +29,26 @@ import org.springframework.util.Assert;
|
|||||||
*/
|
*/
|
||||||
public class InvocationContext {
|
public class InvocationContext {
|
||||||
|
|
||||||
private final Principal principal;
|
private final SecurityContext securityContext;
|
||||||
|
|
||||||
private final Map<String, Object> arguments;
|
private final Map<String, Object> arguments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new context for an operation being invoked by the given {@code principal}
|
* Creates a new context for an operation being invoked by the given {@code principal}
|
||||||
* with the given available {@code arguments}.
|
* with the given available {@code arguments}.
|
||||||
* @param principal the principal invoking the operation. May be {@code null}
|
* @param securityContext the current security context. Never {@code null}
|
||||||
* @param arguments the arguments available to the operation. Never {@code null}
|
* @param arguments the arguments available to the operation. Never {@code null}
|
||||||
*/
|
*/
|
||||||
public InvocationContext(Principal principal, Map<String, Object> arguments) {
|
public InvocationContext(SecurityContext securityContext,
|
||||||
|
Map<String, Object> arguments) {
|
||||||
|
Assert.notNull(securityContext, "SecurityContext must not be null");
|
||||||
Assert.notNull(arguments, "Arguments must not be null");
|
Assert.notNull(arguments, "Arguments must not be null");
|
||||||
this.principal = principal;
|
this.securityContext = securityContext;
|
||||||
this.arguments = arguments;
|
this.arguments = arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Principal getPrincipal() {
|
public SecurityContext getSecurityContext() {
|
||||||
return this.principal;
|
return this.securityContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> getArguments() {
|
public Map<String, Object> getArguments() {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.actuate.endpoint;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Security context in which an endpoint is being invoked.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public interface SecurityContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the currently authenticated {@link Principal} or {@code null}.
|
||||||
|
* @return the principal or {@code null}
|
||||||
|
*/
|
||||||
|
Principal getPrincipal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the currently authenticated user is in the given
|
||||||
|
* {@code role}, or false otherwise.
|
||||||
|
* @param role name of the role
|
||||||
|
* @return {@code true} if the user is in the given role
|
||||||
|
*/
|
||||||
|
boolean isUserInRole(String role);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import java.util.Set;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
|
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
|
||||||
@@ -89,7 +90,10 @@ public class ReflectiveOperationInvoker implements OperationInvoker {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (Principal.class.equals(parameter.getType())) {
|
if (Principal.class.equals(parameter.getType())) {
|
||||||
return context.getPrincipal() == null;
|
return context.getSecurityContext().getPrincipal() == null;
|
||||||
|
}
|
||||||
|
if (SecurityContext.class.equals(parameter.getType())) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return context.getArguments().get(parameter.getName()) == null;
|
return context.getArguments().get(parameter.getName()) == null;
|
||||||
}
|
}
|
||||||
@@ -102,7 +106,10 @@ public class ReflectiveOperationInvoker implements OperationInvoker {
|
|||||||
private Object resolveArgument(OperationParameter parameter,
|
private Object resolveArgument(OperationParameter parameter,
|
||||||
InvocationContext context) {
|
InvocationContext context) {
|
||||||
if (Principal.class.equals(parameter.getType())) {
|
if (Principal.class.equals(parameter.getType())) {
|
||||||
return context.getPrincipal();
|
return context.getSecurityContext().getPrincipal();
|
||||||
|
}
|
||||||
|
if (SecurityContext.class.equals(parameter.getType())) {
|
||||||
|
return context.getSecurityContext();
|
||||||
}
|
}
|
||||||
Object value = context.getArguments().get(parameter.getName());
|
Object value = context.getArguments().get(parameter.getName());
|
||||||
return this.parameterValueMapper.mapParameterValue(parameter, value);
|
return this.parameterValueMapper.mapParameterValue(parameter, value);
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public class CachingOperationInvoker implements OperationInvoker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasInput(InvocationContext context) {
|
private boolean hasInput(InvocationContext context) {
|
||||||
if (context.getPrincipal() != null) {
|
if (context.getSecurityContext().getPrincipal() != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Map<String, Object> arguments = context.getArguments();
|
Map<String, Object> arguments = context.getArguments();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint.jmx;
|
package org.springframework.boot.actuate.endpoint.jmx;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -33,6 +34,7 @@ import reactor.core.publisher.Mono;
|
|||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
@@ -97,7 +99,8 @@ public class EndpointMBean implements DynamicMBean {
|
|||||||
String[] parameterNames = operation.getParameters().stream()
|
String[] parameterNames = operation.getParameters().stream()
|
||||||
.map(JmxOperationParameter::getName).toArray(String[]::new);
|
.map(JmxOperationParameter::getName).toArray(String[]::new);
|
||||||
Map<String, Object> arguments = getArguments(parameterNames, params);
|
Map<String, Object> arguments = getArguments(parameterNames, params);
|
||||||
Object result = operation.invoke(new InvocationContext(null, arguments));
|
Object result = operation
|
||||||
|
.invoke(new InvocationContext(new JmxSecurityContext(), arguments));
|
||||||
if (REACTOR_PRESENT) {
|
if (REACTOR_PRESENT) {
|
||||||
result = ReactiveHandler.handle(result);
|
result = ReactiveHandler.handle(result);
|
||||||
}
|
}
|
||||||
@@ -149,4 +152,18 @@ public class EndpointMBean implements DynamicMBean {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class JmxSecurityContext implements SecurityContext {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal getPrincipal() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserInRole(String role) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.web.jersey;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.security.Principal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -40,6 +41,7 @@ import reactor.core.publisher.Mono;
|
|||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||||
@@ -150,7 +152,7 @@ public class JerseyEndpointResourceFactory {
|
|||||||
arguments.putAll(extractQueryParameters(data));
|
arguments.putAll(extractQueryParameters(data));
|
||||||
try {
|
try {
|
||||||
Object response = this.operation.invoke(new InvocationContext(
|
Object response = this.operation.invoke(new InvocationContext(
|
||||||
data.getSecurityContext().getUserPrincipal(), arguments));
|
new JerseySecurityContext(data.getSecurityContext()), arguments));
|
||||||
return convertToJaxRsResponse(response, data.getRequest().getMethod());
|
return convertToJaxRsResponse(response, data.getRequest().getMethod());
|
||||||
}
|
}
|
||||||
catch (InvalidEndpointRequestException ex) {
|
catch (InvalidEndpointRequestException ex) {
|
||||||
@@ -275,4 +277,24 @@ public class JerseyEndpointResourceFactory {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class JerseySecurityContext implements SecurityContext {
|
||||||
|
|
||||||
|
private final javax.ws.rs.core.SecurityContext securityContext;
|
||||||
|
|
||||||
|
private JerseySecurityContext(javax.ws.rs.core.SecurityContext securityContext) {
|
||||||
|
this.securityContext = securityContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal getPrincipal() {
|
||||||
|
return this.securityContext.getUserPrincipal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserInRole(String role) {
|
||||||
|
return this.securityContext.isUserInRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.security.Principal;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
@@ -30,6 +31,7 @@ import reactor.core.scheduler.Schedulers;
|
|||||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||||
@@ -40,6 +42,10 @@ import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicat
|
|||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -256,33 +262,55 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
|
|||||||
private static final class ReactiveWebOperationAdapter
|
private static final class ReactiveWebOperationAdapter
|
||||||
implements ReactiveWebOperation {
|
implements ReactiveWebOperation {
|
||||||
|
|
||||||
private static final Principal NO_PRINCIPAL = new Principal() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
private final OperationInvoker invoker;
|
private final OperationInvoker invoker;
|
||||||
|
|
||||||
|
private final Supplier<Mono<? extends SecurityContext>> securityContextSupplier;
|
||||||
|
|
||||||
private ReactiveWebOperationAdapter(OperationInvoker invoker) {
|
private ReactiveWebOperationAdapter(OperationInvoker invoker) {
|
||||||
this.invoker = invoker;
|
this.invoker = invoker;
|
||||||
|
if (ClassUtils.isPresent(
|
||||||
|
"org.springframework.security.core.context.ReactiveSecurityContextHolder",
|
||||||
|
getClass().getClassLoader())) {
|
||||||
|
this.securityContextSupplier = this::springSecurityContext;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.securityContextSupplier = this::emptySecurityContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<? extends SecurityContext> springSecurityContext() {
|
||||||
|
return ReactiveSecurityContextHolder.getContext()
|
||||||
|
.map((securityContext) -> new ReactiveSecurityContext(
|
||||||
|
securityContext.getAuthentication()))
|
||||||
|
.switchIfEmpty(Mono.just(new ReactiveSecurityContext(null)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SecurityContext> emptySecurityContext() {
|
||||||
|
return Mono.just(new SecurityContext() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal getPrincipal() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserInRole(String role) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange,
|
public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange,
|
||||||
Map<String, String> body) {
|
Map<String, String> body) {
|
||||||
return exchange.getPrincipal().defaultIfEmpty(NO_PRINCIPAL)
|
Map<String, Object> arguments = getArguments(exchange, body);
|
||||||
.flatMap((principal) -> {
|
return this.securityContextSupplier.get()
|
||||||
Map<String, Object> arguments = getArguments(exchange, body);
|
.map((securityContext) -> new InvocationContext(securityContext,
|
||||||
return handleResult(
|
arguments))
|
||||||
(Publisher<?>) this.invoker.invoke(new InvocationContext(
|
.flatMap((invocationContext) -> handleResult(
|
||||||
principal == NO_PRINCIPAL ? null : principal,
|
(Publisher<?>) this.invoker.invoke(invocationContext),
|
||||||
arguments)),
|
exchange.getRequest().getMethod()));
|
||||||
exchange.getRequest().getMethod());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> getArguments(ServerWebExchange exchange,
|
private Map<String, Object> getArguments(ServerWebExchange exchange,
|
||||||
@@ -358,4 +386,29 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ReactiveSecurityContext implements SecurityContext {
|
||||||
|
|
||||||
|
private final Authentication authentication;
|
||||||
|
|
||||||
|
ReactiveSecurityContext(Authentication authentication) {
|
||||||
|
this.authentication = authentication;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal getPrincipal() {
|
||||||
|
return this.authentication;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserInRole(String role) {
|
||||||
|
if (this.authentication == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return AuthorityReactiveAuthorizationManager.hasRole(role)
|
||||||
|
.check(Mono.just(this.authentication), null).block().isGranted();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.boot.actuate.endpoint.web.servlet;
|
package org.springframework.boot.actuate.endpoint.web.servlet;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.security.Principal;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -29,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||||
@@ -243,7 +245,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
|
|||||||
try {
|
try {
|
||||||
return handleResult(
|
return handleResult(
|
||||||
this.invoker.invoke(new InvocationContext(
|
this.invoker.invoke(new InvocationContext(
|
||||||
request.getUserPrincipal(), arguments)),
|
new ServletSecurityContext(request), arguments)),
|
||||||
HttpMethod.valueOf(request.getMethod()));
|
HttpMethod.valueOf(request.getMethod()));
|
||||||
}
|
}
|
||||||
catch (InvalidEndpointRequestException ex) {
|
catch (InvalidEndpointRequestException ex) {
|
||||||
@@ -312,4 +314,24 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ServletSecurityContext implements SecurityContext {
|
||||||
|
|
||||||
|
private final HttpServletRequest request;
|
||||||
|
|
||||||
|
private ServletSecurityContext(HttpServletRequest request) {
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal getPrincipal() {
|
||||||
|
return this.request.getUserPrincipal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserInRole(String role) {
|
||||||
|
return this.request.isUserInRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,10 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.health;
|
package org.springframework.boot.actuate.health;
|
||||||
|
|
||||||
import java.security.Principal;
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link EndpointWebExtension} for the {@link HealthEndpoint}.
|
* {@link EndpointWebExtension} for the {@link HealthEndpoint}.
|
||||||
@@ -39,31 +37,23 @@ public class HealthEndpointWebExtension {
|
|||||||
|
|
||||||
private final HealthIndicator delegate;
|
private final HealthIndicator delegate;
|
||||||
|
|
||||||
private final HealthStatusHttpMapper statusHttpMapper;
|
private final HealthWebEndpointResponseMapper responseMapper;
|
||||||
|
|
||||||
private final ShowDetails showDetails;
|
|
||||||
|
|
||||||
public HealthEndpointWebExtension(HealthIndicator delegate,
|
public HealthEndpointWebExtension(HealthIndicator delegate,
|
||||||
HealthStatusHttpMapper statusHttpMapper, ShowDetails showDetails) {
|
HealthWebEndpointResponseMapper responseMapper) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
this.statusHttpMapper = statusHttpMapper;
|
this.responseMapper = responseMapper;
|
||||||
this.showDetails = showDetails;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
public WebEndpointResponse<Health> getHealth(@Nullable Principal principal) {
|
public WebEndpointResponse<Health> getHealth(SecurityContext securityContext) {
|
||||||
return getHealth(principal, this.showDetails);
|
return this.responseMapper.map(this.delegate.health(), securityContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebEndpointResponse<Health> getHealth(Principal principal,
|
public WebEndpointResponse<Health> getHealth(SecurityContext securityContext,
|
||||||
ShowDetails showDetails) {
|
ShowDetails showDetails) {
|
||||||
Health health = this.delegate.health();
|
return this.responseMapper.map(this.delegate.health(), securityContext,
|
||||||
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
|
showDetails);
|
||||||
if (showDetails == ShowDetails.NEVER
|
|
||||||
|| (showDetails == ShowDetails.WHEN_AUTHENTICATED && principal == null)) {
|
|
||||||
health = Health.status(health.getStatus()).build();
|
|
||||||
}
|
|
||||||
return new WebEndpointResponse<>(health, status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.actuate.health;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a {@link Health} to a {@WebEndpointResponse}.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class HealthWebEndpointResponseMapper {
|
||||||
|
|
||||||
|
private final HealthStatusHttpMapper statusHttpMapper;
|
||||||
|
|
||||||
|
private final ShowDetails showDetails;
|
||||||
|
|
||||||
|
private final Set<String> authorizedRoles;
|
||||||
|
|
||||||
|
public HealthWebEndpointResponseMapper(HealthStatusHttpMapper statusHttpMapper,
|
||||||
|
ShowDetails showDetails, Set<String> authorizedRoles) {
|
||||||
|
this.statusHttpMapper = statusHttpMapper;
|
||||||
|
this.showDetails = showDetails;
|
||||||
|
this.authorizedRoles = authorizedRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given {@code health} to a {@link WebEndpointResponse}, honouring the
|
||||||
|
* mapper's default {@link ShowDetails} using the given {@code securityContext}.
|
||||||
|
* @param health the health to map
|
||||||
|
* @param securityContext the security context
|
||||||
|
* @return the mapped response
|
||||||
|
*/
|
||||||
|
public WebEndpointResponse<Health> map(Health health,
|
||||||
|
SecurityContext securityContext) {
|
||||||
|
return map(health, securityContext, this.showDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given {@code health} to a {@link WebEndpointResponse}, honouring the given
|
||||||
|
* {@showDetails} using the given {@code securityContext}.
|
||||||
|
* @param health the health to map
|
||||||
|
* @param securityContext the security context
|
||||||
|
* @param showDetails when to show details in the response
|
||||||
|
* @return the mapped response
|
||||||
|
*/
|
||||||
|
public WebEndpointResponse<Health> map(Health health, SecurityContext securityContext,
|
||||||
|
ShowDetails showDetails) {
|
||||||
|
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
|
||||||
|
if (showDetails == ShowDetails.NEVER
|
||||||
|
|| (showDetails == ShowDetails.WHEN_AUTHORIZED
|
||||||
|
&& (securityContext.getPrincipal() == null
|
||||||
|
|| !isUserInRole(securityContext)))) {
|
||||||
|
health = Health.status(health.getStatus()).build();
|
||||||
|
}
|
||||||
|
return new WebEndpointResponse<>(health, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUserInRole(SecurityContext securityContext) {
|
||||||
|
if (CollectionUtils.isEmpty(this.authorizedRoles)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (String role : this.authorizedRoles) {
|
||||||
|
if (securityContext.isUserInRole(role)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,14 +16,12 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.health;
|
package org.springframework.boot.actuate.health;
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reactive {@link EndpointWebExtension} for the {@link HealthEndpoint}.
|
* Reactive {@link EndpointWebExtension} for the {@link HealthEndpoint}.
|
||||||
@@ -36,33 +34,24 @@ public class ReactiveHealthEndpointWebExtension {
|
|||||||
|
|
||||||
private final ReactiveHealthIndicator delegate;
|
private final ReactiveHealthIndicator delegate;
|
||||||
|
|
||||||
private final HealthStatusHttpMapper statusHttpMapper;
|
private final HealthWebEndpointResponseMapper responseMapper;
|
||||||
|
|
||||||
private final ShowDetails showDetails;
|
|
||||||
|
|
||||||
public ReactiveHealthEndpointWebExtension(ReactiveHealthIndicator delegate,
|
public ReactiveHealthEndpointWebExtension(ReactiveHealthIndicator delegate,
|
||||||
HealthStatusHttpMapper statusHttpMapper, ShowDetails showDetails) {
|
HealthWebEndpointResponseMapper responseMapper) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
this.statusHttpMapper = statusHttpMapper;
|
this.responseMapper = responseMapper;
|
||||||
this.showDetails = showDetails;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
public Mono<WebEndpointResponse<Health>> health(@Nullable Principal principal) {
|
public Mono<WebEndpointResponse<Health>> health(SecurityContext securityContext) {
|
||||||
return health(principal, this.showDetails);
|
return this.delegate.health()
|
||||||
|
.map((health) -> this.responseMapper.map(health, securityContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<WebEndpointResponse<Health>> health(Principal principal,
|
public Mono<WebEndpointResponse<Health>> health(SecurityContext securityContext,
|
||||||
ShowDetails showDetails) {
|
ShowDetails showDetails) {
|
||||||
return this.delegate.health().map((health) -> {
|
return this.delegate.health().map((health) -> this.responseMapper.map(health,
|
||||||
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
|
securityContext, showDetails));
|
||||||
if (showDetails == ShowDetails.NEVER
|
|
||||||
|| (showDetails == ShowDetails.WHEN_AUTHENTICATED
|
|
||||||
&& principal == null)) {
|
|
||||||
health = Health.status(health.getStatus()).build();
|
|
||||||
}
|
|
||||||
return new WebEndpointResponse<>(health, status);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ public enum ShowDetails {
|
|||||||
NEVER,
|
NEVER,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show details in the response when accessed by an authenticated user.
|
* Show details in the response when accessed by an authorized user.
|
||||||
*/
|
*/
|
||||||
WHEN_AUTHENTICATED,
|
WHEN_AUTHORIZED,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Always show details in the response.
|
* Always show details in the response.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
|
||||||
@@ -34,6 +35,7 @@ import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
|||||||
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
|
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link DiscoveredOperationsFactory}.
|
* Tests for {@link DiscoveredOperationsFactory}.
|
||||||
@@ -106,7 +108,8 @@ public class DiscoveredOperationsFactoryTests {
|
|||||||
TestOperation operation = getFirst(
|
TestOperation operation = getFirst(
|
||||||
this.factory.createOperations("test", new ExampleWithParams()));
|
this.factory.createOperations("test", new ExampleWithParams()));
|
||||||
Map<String, Object> params = Collections.singletonMap("name", 123);
|
Map<String, Object> params = Collections.singletonMap("name", 123);
|
||||||
Object result = operation.invoke(new InvocationContext(null, params));
|
Object result = operation
|
||||||
|
.invoke(new InvocationContext(mock(SecurityContext.class), params));
|
||||||
assertThat(result).isEqualTo("123");
|
assertThat(result).isEqualTo("123");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +119,8 @@ public class DiscoveredOperationsFactoryTests {
|
|||||||
this.invokerAdvisors.add(advisor);
|
this.invokerAdvisors.add(advisor);
|
||||||
TestOperation operation = getFirst(
|
TestOperation operation = getFirst(
|
||||||
this.factory.createOperations("test", new ExampleRead()));
|
this.factory.createOperations("test", new ExampleRead()));
|
||||||
operation.invoke(new InvocationContext(null, Collections.emptyMap()));
|
operation.invoke(new InvocationContext(mock(SecurityContext.class),
|
||||||
|
Collections.emptyMap()));
|
||||||
assertThat(advisor.getEndpointId()).isEqualTo("test");
|
assertThat(advisor.getEndpointId()).isEqualTo("test");
|
||||||
assertThat(advisor.getOperationType()).isEqualTo(OperationType.READ);
|
assertThat(advisor.getOperationType()).isEqualTo(OperationType.READ);
|
||||||
assertThat(advisor.getParameters()).isEmpty();
|
assertThat(advisor.getParameters()).isEmpty();
|
||||||
|
|||||||
@@ -25,12 +25,14 @@ import org.junit.rules.ExpectedException;
|
|||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
|
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link ReflectiveOperationInvoker}.
|
* Tests for {@link ReflectiveOperationInvoker}.
|
||||||
@@ -84,8 +86,8 @@ public class ReflectiveOperationInvokerTests {
|
|||||||
public void invokeShouldInvokeMethod() {
|
public void invokeShouldInvokeMethod() {
|
||||||
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
||||||
this.operationMethod, this.parameterValueMapper);
|
this.operationMethod, this.parameterValueMapper);
|
||||||
Object result = invoker.invoke(
|
Object result = invoker.invoke(new InvocationContext(mock(SecurityContext.class),
|
||||||
new InvocationContext(null, Collections.singletonMap("name", "boot")));
|
Collections.singletonMap("name", "boot")));
|
||||||
assertThat(result).isEqualTo("toob");
|
assertThat(result).isEqualTo("toob");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,8 +96,8 @@ public class ReflectiveOperationInvokerTests {
|
|||||||
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
||||||
this.operationMethod, this.parameterValueMapper);
|
this.operationMethod, this.parameterValueMapper);
|
||||||
this.thrown.expect(MissingParametersException.class);
|
this.thrown.expect(MissingParametersException.class);
|
||||||
invoker.invoke(
|
invoker.invoke(new InvocationContext(mock(SecurityContext.class),
|
||||||
new InvocationContext(null, Collections.singletonMap("name", null)));
|
Collections.singletonMap("name", null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -104,8 +106,8 @@ public class ReflectiveOperationInvokerTests {
|
|||||||
Example.class, "reverseNullable", String.class), OperationType.READ);
|
Example.class, "reverseNullable", String.class), OperationType.READ);
|
||||||
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
||||||
operationMethod, this.parameterValueMapper);
|
operationMethod, this.parameterValueMapper);
|
||||||
Object result = invoker.invoke(
|
Object result = invoker.invoke(new InvocationContext(mock(SecurityContext.class),
|
||||||
new InvocationContext(null, Collections.singletonMap("name", null)));
|
Collections.singletonMap("name", null)));
|
||||||
assertThat(result).isEqualTo("llun");
|
assertThat(result).isEqualTo("llun");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,8 +115,8 @@ public class ReflectiveOperationInvokerTests {
|
|||||||
public void invokeShouldResolveParameters() {
|
public void invokeShouldResolveParameters() {
|
||||||
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
||||||
this.operationMethod, this.parameterValueMapper);
|
this.operationMethod, this.parameterValueMapper);
|
||||||
Object result = invoker.invoke(
|
Object result = invoker.invoke(new InvocationContext(mock(SecurityContext.class),
|
||||||
new InvocationContext(null, Collections.singletonMap("name", 1234)));
|
Collections.singletonMap("name", 1234)));
|
||||||
assertThat(result).isEqualTo("4321");
|
assertThat(result).isEqualTo("4321");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.junit.Test;
|
|||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@@ -67,7 +68,8 @@ public class CachingOperationInvokerTests {
|
|||||||
private void assertCacheIsUsed(Map<String, Object> parameters) {
|
private void assertCacheIsUsed(Map<String, Object> parameters) {
|
||||||
OperationInvoker target = mock(OperationInvoker.class);
|
OperationInvoker target = mock(OperationInvoker.class);
|
||||||
Object expected = new Object();
|
Object expected = new Object();
|
||||||
InvocationContext context = new InvocationContext(null, parameters);
|
InvocationContext context = new InvocationContext(mock(SecurityContext.class),
|
||||||
|
parameters);
|
||||||
given(target.invoke(context)).willReturn(expected);
|
given(target.invoke(context)).willReturn(expected);
|
||||||
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
|
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
|
||||||
Object response = invoker.invoke(context);
|
Object response = invoker.invoke(context);
|
||||||
@@ -84,7 +86,8 @@ public class CachingOperationInvokerTests {
|
|||||||
Map<String, Object> parameters = new HashMap<>();
|
Map<String, Object> parameters = new HashMap<>();
|
||||||
parameters.put("test", "value");
|
parameters.put("test", "value");
|
||||||
parameters.put("something", null);
|
parameters.put("something", null);
|
||||||
InvocationContext context = new InvocationContext(null, parameters);
|
InvocationContext context = new InvocationContext(mock(SecurityContext.class),
|
||||||
|
parameters);
|
||||||
given(target.invoke(context)).willReturn(new Object());
|
given(target.invoke(context)).willReturn(new Object());
|
||||||
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
|
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
|
||||||
invoker.invoke(context);
|
invoker.invoke(context);
|
||||||
@@ -97,7 +100,8 @@ public class CachingOperationInvokerTests {
|
|||||||
public void targetInvokedWhenCacheExpires() throws InterruptedException {
|
public void targetInvokedWhenCacheExpires() throws InterruptedException {
|
||||||
OperationInvoker target = mock(OperationInvoker.class);
|
OperationInvoker target = mock(OperationInvoker.class);
|
||||||
Map<String, Object> parameters = new HashMap<>();
|
Map<String, Object> parameters = new HashMap<>();
|
||||||
InvocationContext context = new InvocationContext(null, parameters);
|
InvocationContext context = new InvocationContext(mock(SecurityContext.class),
|
||||||
|
parameters);
|
||||||
given(target.invoke(context)).willReturn(new Object());
|
given(target.invoke(context)).willReturn(new Object());
|
||||||
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 50L);
|
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 50L);
|
||||||
invoker.invoke(context);
|
invoker.invoke(context);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import java.util.function.Supplier;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
@@ -361,6 +362,52 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||||||
.expectBody(String.class).isEqualTo("Zoe"));
|
.expectBody(String.class).isEqualTo("Zoe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void securityContextIsAvailableAndHasNullPrincipalWhenRequestHasNoPrincipal() {
|
||||||
|
load(SecurityContextEndpointConfiguration.class,
|
||||||
|
(client) -> client.get().uri("/securitycontext")
|
||||||
|
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||||
|
.isOk().expectBody(String.class).isEqualTo("None"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void securityContextIsAvailableAndHasPrincipalWhenRequestHasPrincipal() {
|
||||||
|
load((context) -> {
|
||||||
|
this.authenticatedContextCustomizer.accept(context);
|
||||||
|
context.register(SecurityContextEndpointConfiguration.class);
|
||||||
|
}, (client) -> client.get().uri("/securitycontext")
|
||||||
|
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk()
|
||||||
|
.expectBody(String.class).isEqualTo("Alice"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void userInRoleReturnsFalseWhenRequestHasNoPrincipal() {
|
||||||
|
load(UserInRoleEndpointConfiguration.class,
|
||||||
|
(client) -> client.get().uri("/userinrole?role=ADMIN")
|
||||||
|
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
|
||||||
|
.isOk().expectBody(String.class).isEqualTo("ADMIN: false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void userInRoleReturnsFalseWhenUserIsNotInRole() {
|
||||||
|
load((context) -> {
|
||||||
|
this.authenticatedContextCustomizer.accept(context);
|
||||||
|
context.register(UserInRoleEndpointConfiguration.class);
|
||||||
|
}, (client) -> client.get().uri("/userinrole?role=ADMIN")
|
||||||
|
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk()
|
||||||
|
.expectBody(String.class).isEqualTo("ADMIN: false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void userInRoleReturnsTrueWhenUserIsInRole() {
|
||||||
|
load((context) -> {
|
||||||
|
this.authenticatedContextCustomizer.accept(context);
|
||||||
|
context.register(UserInRoleEndpointConfiguration.class);
|
||||||
|
}, (client) -> client.get().uri("/userinrole?role=ACTUATOR")
|
||||||
|
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk()
|
||||||
|
.expectBody(String.class).isEqualTo("ACTUATOR: true"));
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract int getPort(T context);
|
protected abstract int getPort(T context);
|
||||||
|
|
||||||
protected void validateErrorBody(WebTestClient.BodyContentSpec body,
|
protected void validateErrorBody(WebTestClient.BodyContentSpec body,
|
||||||
@@ -581,6 +628,28 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(BaseConfiguration.class)
|
||||||
|
protected static class SecurityContextEndpointConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityContextEndpoint securityContextEndpoint() {
|
||||||
|
return new SecurityContextEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(BaseConfiguration.class)
|
||||||
|
protected static class UserInRoleEndpointConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UserInRoleEndpoint userInRoleEndpoint() {
|
||||||
|
return new UserInRoleEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Endpoint(id = "test")
|
@Endpoint(id = "test")
|
||||||
static class TestEndpoint {
|
static class TestEndpoint {
|
||||||
|
|
||||||
@@ -779,6 +848,27 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Endpoint(id = "securitycontext")
|
||||||
|
static class SecurityContextEndpoint {
|
||||||
|
|
||||||
|
@ReadOperation
|
||||||
|
public String read(SecurityContext securityContext) {
|
||||||
|
Principal principal = securityContext.getPrincipal();
|
||||||
|
return principal == null ? "None" : principal.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Endpoint(id = "userinrole")
|
||||||
|
static class UserInRoleEndpoint {
|
||||||
|
|
||||||
|
@ReadOperation
|
||||||
|
public String read(SecurityContext securityContext, String role) {
|
||||||
|
return role + ": " + securityContext.isUserInRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public interface EndpointDelegate {
|
public interface EndpointDelegate {
|
||||||
|
|
||||||
void write();
|
void write();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
package org.springframework.boot.actuate.endpoint.web.jersey;
|
package org.springframework.boot.actuate.endpoint.web.jersey;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.Principal;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
@@ -25,7 +25,6 @@ import javax.servlet.Filter;
|
|||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.ws.rs.ext.ContextResolver;
|
import javax.ws.rs.ext.ContextResolver;
|
||||||
|
|
||||||
@@ -47,6 +46,11 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
@@ -131,7 +135,18 @@ public class JerseyWebEndpointIntegrationTests extends
|
|||||||
protected void doFilterInternal(HttpServletRequest request,
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
HttpServletResponse response, FilterChain filterChain)
|
HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
filterChain.doFilter(new MockPrincipalWrapper(request), response);
|
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||||
|
context.setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||||
|
"Alice", "secret",
|
||||||
|
Arrays.asList(new SimpleGrantedAuthority("ROLE_ACTUATOR"))));
|
||||||
|
SecurityContextHolder.setContext(context);
|
||||||
|
try {
|
||||||
|
filterChain.doFilter(new SecurityContextHolderAwareRequestWrapper(
|
||||||
|
request, "ROLE_"), response);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -139,28 +154,6 @@ public class JerseyWebEndpointIntegrationTests extends
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MockPrincipalWrapper extends HttpServletRequestWrapper {
|
|
||||||
|
|
||||||
MockPrincipalWrapper(HttpServletRequest request) {
|
|
||||||
super(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Principal getUserPrincipal() {
|
|
||||||
return new MockPrincipal();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MockPrincipal implements Principal {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "Alice";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class ObjectMapperContextResolver
|
private static final class ObjectMapperContextResolver
|
||||||
implements ContextResolver<ObjectMapper> {
|
implements ContextResolver<ObjectMapper> {
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint.web.reactive;
|
package org.springframework.boot.actuate.endpoint.web.reactive;
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -40,10 +39,12 @@ import org.springframework.core.env.Environment;
|
|||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.server.reactive.HttpHandler;
|
import org.springframework.http.server.reactive.HttpHandler;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.ServerWebExchangeDecorator;
|
|
||||||
import org.springframework.web.server.WebFilter;
|
import org.springframework.web.server.WebFilter;
|
||||||
import org.springframework.web.server.WebFilterChain;
|
import org.springframework.web.server.WebFilterChain;
|
||||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||||
@@ -152,8 +153,12 @@ public class WebFluxEndpointIntegrationTests extends
|
|||||||
@Override
|
@Override
|
||||||
public Mono<Void> filter(ServerWebExchange exchange,
|
public Mono<Void> filter(ServerWebExchange exchange,
|
||||||
WebFilterChain chain) {
|
WebFilterChain chain) {
|
||||||
return chain.filter(
|
return chain.filter(exchange).subscriberContext(
|
||||||
new MockPrincipalServerWebExchangeDecorator(exchange));
|
ReactiveSecurityContextHolder.withAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken("Alice",
|
||||||
|
"secret",
|
||||||
|
Arrays.asList(new SimpleGrantedAuthority(
|
||||||
|
"ROLE_ACTUATOR")))));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -161,27 +166,4 @@ public class WebFluxEndpointIntegrationTests extends
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MockPrincipalServerWebExchangeDecorator
|
|
||||||
extends ServerWebExchangeDecorator {
|
|
||||||
|
|
||||||
MockPrincipalServerWebExchangeDecorator(ServerWebExchange delegate) {
|
|
||||||
super(delegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Principal> getPrincipal() {
|
|
||||||
return Mono.just(new MockPrincipal());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MockPrincipal implements Principal {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "Alice";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,12 @@
|
|||||||
package org.springframework.boot.actuate.endpoint.web.servlet;
|
package org.springframework.boot.actuate.endpoint.web.servlet;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -48,6 +46,11 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
@@ -145,7 +148,18 @@ public class MvcWebEndpointIntegrationTests extends
|
|||||||
protected void doFilterInternal(HttpServletRequest request,
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
HttpServletResponse response, FilterChain filterChain)
|
HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
filterChain.doFilter(new MockPrincipalWrapper(request), response);
|
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||||
|
context.setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||||
|
"Alice", "secret",
|
||||||
|
Arrays.asList(new SimpleGrantedAuthority("ROLE_ACTUATOR"))));
|
||||||
|
SecurityContextHolder.setContext(context);
|
||||||
|
try {
|
||||||
|
filterChain.doFilter(new SecurityContextHolderAwareRequestWrapper(
|
||||||
|
request, "ROLE_"), response);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -153,26 +167,4 @@ public class MvcWebEndpointIntegrationTests extends
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MockPrincipalWrapper extends HttpServletRequestWrapper {
|
|
||||||
|
|
||||||
MockPrincipalWrapper(HttpServletRequest request) {
|
|
||||||
super(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Principal getUserPrincipal() {
|
|
||||||
return new MockPrincipal();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MockPrincipal implements Principal {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "Alice";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.springframework.boot.actuate.health;
|
package org.springframework.boot.actuate.health;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -75,7 +77,9 @@ public class HealthEndpointWebIntegrationTests {
|
|||||||
return new HealthEndpointWebExtension(
|
return new HealthEndpointWebExtension(
|
||||||
new CompositeHealthIndicatorFactory().createHealthIndicator(
|
new CompositeHealthIndicatorFactory().createHealthIndicator(
|
||||||
new OrderedHealthAggregator(), healthIndicators),
|
new OrderedHealthAggregator(), healthIndicators),
|
||||||
new HealthStatusHttpMapper(), ShowDetails.ALWAYS);
|
new HealthWebEndpointResponseMapper(new HealthStatusHttpMapper(),
|
||||||
|
ShowDetails.ALWAYS,
|
||||||
|
new HashSet<>(Arrays.asList("ACTUATOR"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -1200,7 +1200,8 @@ content into your application. Rather, pick only the properties that you need.
|
|||||||
# HEALTH ENDPOINT ({sc-spring-boot-actuator}/health/HealthEndpoint.{sc-ext}[HealthEndpoint], {sc-spring-boot-actuator-autoconfigure}/health/HealthEndpointProperties.{sc-ext}[HealthEndpointProperties])
|
# HEALTH ENDPOINT ({sc-spring-boot-actuator}/health/HealthEndpoint.{sc-ext}[HealthEndpoint], {sc-spring-boot-actuator-autoconfigure}/health/HealthEndpointProperties.{sc-ext}[HealthEndpointProperties])
|
||||||
management.endpoint.health.cache.time-to-live=0ms # Maximum time that a response can be cached.
|
management.endpoint.health.cache.time-to-live=0ms # Maximum time that a response can be cached.
|
||||||
management.endpoint.health.enabled= # Whether to enable the health endpoint.
|
management.endpoint.health.enabled= # Whether to enable the health endpoint.
|
||||||
management.endpoint.health.show-details=false # Whether to show full health details instead of just the status when exposed over a potentially insecure connection.
|
management.endpoint.health.roles= # Roles used to determine whether or not a user is authorized to be shown details. When empty, all authenticated users are authorized.
|
||||||
|
management.endpoint.health.show-details=when-authorized # When to show full health details.
|
||||||
|
|
||||||
# HEAP DUMP ENDPOINT ({sc-spring-boot-actuator}/management/HeapDumpWebEndpoint.{sc-ext}[HeapDumpWebEndpoint])
|
# HEAP DUMP ENDPOINT ({sc-spring-boot-actuator}/management/HeapDumpWebEndpoint.{sc-ext}[HeapDumpWebEndpoint])
|
||||||
management.endpoint.heapdump.cache.time-to-live=0ms # Maximum time that a response can be cached.
|
management.endpoint.heapdump.cache.time-to-live=0ms # Maximum time that a response can be cached.
|
||||||
|
|||||||
@@ -523,14 +523,18 @@ following values:
|
|||||||
|`never`
|
|`never`
|
||||||
|Details are never shown.
|
|Details are never shown.
|
||||||
|
|
||||||
|`when-authenticated`
|
|`when-authorized`
|
||||||
|Details are only shown to authenticated users.
|
|Details are only shown to authorized users. Authorized roles can be configured using
|
||||||
|
`management.endpoint.health.roles`.
|
||||||
|
|
||||||
|`always`
|
|`always`
|
||||||
|Details are shown to all users.
|
|Details are shown to all users.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
The default value is `when-authenticated`.
|
The default value is `when-authorized`. A user is considered to be authorized when they
|
||||||
|
are in one or more of the endpoint's roles. If the endpoint has no configured roles
|
||||||
|
(the default) all authenticated users are considered to be authorized. The roles can
|
||||||
|
be configured using the `management.endpoint.health.roles` property.
|
||||||
|
|
||||||
NOTE: If you have secured your application and wish to use `always`, your security
|
NOTE: If you have secured your application and wish to use `always`, your security
|
||||||
configuration must permit access to the health endpoint for both authenticated and
|
configuration must permit access to the health endpoint for both authenticated and
|
||||||
|
|||||||
Reference in New Issue
Block a user