Use role-based security to show details in the health endpoint
Closes gh-11869
This commit is contained in:
parent
a5960bc0c3
commit
3e4baf744e
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
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.ShowDetails;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
|
@ -29,9 +32,15 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||
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() {
|
||||
return this.showDetails;
|
||||
|
|
@ -41,4 +50,12 @@ public class HealthEndpointProperties {
|
|||
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.HealthIndicator;
|
||||
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.ReactiveHealthEndpointWebExtension;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
||||
|
|
@ -59,6 +60,15 @@ class HealthEndpointWebExtensionConfiguration {
|
|||
return statusHttpMapper;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public HealthWebEndpointResponseMapper healthWebEndpointResponseMapper(
|
||||
HealthStatusHttpMapper statusHttpMapper,
|
||||
HealthEndpointProperties properties) {
|
||||
return new HealthWebEndpointResponseMapper(statusHttpMapper,
|
||||
properties.getShowDetails(), properties.getRoles());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication(type = Type.REACTIVE)
|
||||
static class ReactiveWebHealthConfiguration {
|
||||
|
|
@ -81,10 +91,9 @@ class HealthEndpointWebExtensionConfiguration {
|
|||
@ConditionalOnEnabledEndpoint
|
||||
@ConditionalOnBean(HealthEndpoint.class)
|
||||
public ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension(
|
||||
HealthStatusHttpMapper healthStatusHttpMapper,
|
||||
HealthEndpointProperties properties) {
|
||||
HealthWebEndpointResponseMapper responseMapper) {
|
||||
return new ReactiveHealthEndpointWebExtension(this.reactiveHealthIndicator,
|
||||
healthStatusHttpMapper, properties.getShowDetails());
|
||||
responseMapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -99,11 +108,10 @@ class HealthEndpointWebExtensionConfiguration {
|
|||
@ConditionalOnBean(HealthEndpoint.class)
|
||||
public HealthEndpointWebExtension healthEndpointWebExtension(
|
||||
ApplicationContext applicationContext,
|
||||
HealthStatusHttpMapper healthStatusHttpMapper,
|
||||
HealthEndpointProperties properties) {
|
||||
HealthWebEndpointResponseMapper responseMapper) {
|
||||
return new HealthEndpointWebExtension(
|
||||
HealthIndicatorBeansComposite.get(applicationContext),
|
||||
healthStatusHttpMapper, properties.getShowDetails());
|
||||
responseMapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.util.function.Function;
|
|||
import org.junit.Test;
|
||||
|
||||
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.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||
|
|
@ -58,8 +59,8 @@ public class CloudFoundryWebEndpointDiscovererTests {
|
|||
for (ExposableWebEndpoint endpoint : endpoints) {
|
||||
if (endpoint.getId().equals("health")) {
|
||||
WebOperation operation = endpoint.getOperations().iterator().next();
|
||||
assertThat(operation
|
||||
.invoke(new InvocationContext(null, Collections.emptyMap())))
|
||||
assertThat(operation.invoke(new InvocationContext(
|
||||
mock(SecurityContext.class), Collections.emptyMap())))
|
||||
.isEqualTo("cf");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,17 +17,19 @@
|
|||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
|
||||
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.HealthStatusHttpMapper;
|
||||
import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
|
|
@ -63,12 +65,17 @@ public class HealthEndpointWebExtensionTests {
|
|||
.withPropertyValues("management.health.status.http-mapping.CUSTOM=500")
|
||||
.run((context) -> {
|
||||
Object extension = context.getBean(HealthEndpointWebExtension.class);
|
||||
HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils
|
||||
.getField(extension, "statusHttpMapper");
|
||||
Map<String, Integer> statusMappings = mapper.getStatusMapping();
|
||||
assertThat(statusMappings).containsEntry("DOWN", 503);
|
||||
assertThat(statusMappings).containsEntry("OUT_OF_SERVICE", 503);
|
||||
assertThat(statusMappings).containsEntry("CUSTOM", 500);
|
||||
HealthWebEndpointResponseMapper responseMapper = (HealthWebEndpointResponseMapper) ReflectionTestUtils
|
||||
.getField(extension, "responseMapper");
|
||||
Class<SecurityContext> securityContext = SecurityContext.class;
|
||||
assertThat(responseMapper
|
||||
.map(Health.down().build(), mock(securityContext))
|
||||
.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) -> {
|
||||
HealthEndpointWebExtension extension = context
|
||||
.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) -> {
|
||||
HealthEndpointWebExtension extension = context
|
||||
.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();
|
||||
});
|
||||
}
|
||||
|
|
@ -110,9 +120,60 @@ public class HealthEndpointWebExtensionTests {
|
|||
.run((context) -> {
|
||||
HealthEndpointWebExtension extension = context
|
||||
.getBean(HealthEndpointWebExtension.class);
|
||||
assertThat(extension.getHealth(mock(Principal.class)).getBody()
|
||||
assertThat(extension.getHealth(mock(SecurityContext.class)).getBody()
|
||||
.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;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
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.HealthEndpoint;
|
||||
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.ReactiveHealthIndicator;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
|
|
@ -69,12 +70,17 @@ public class ReactiveHealthEndpointWebExtensionTests {
|
|||
.run((context) -> {
|
||||
Object extension = context
|
||||
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||
HealthStatusHttpMapper mapper = (HealthStatusHttpMapper) ReflectionTestUtils
|
||||
.getField(extension, "statusHttpMapper");
|
||||
Map<String, Integer> statusMappings = mapper.getStatusMapping();
|
||||
assertThat(statusMappings).containsEntry("DOWN", 503);
|
||||
assertThat(statusMappings).containsEntry("OUT_OF_SERVICE", 503);
|
||||
assertThat(statusMappings).containsEntry("CUSTOM", 500);
|
||||
HealthWebEndpointResponseMapper responseMapper = (HealthWebEndpointResponseMapper) ReflectionTestUtils
|
||||
.getField(extension, "responseMapper");
|
||||
Class<SecurityContext> securityContext = SecurityContext.class;
|
||||
assertThat(responseMapper
|
||||
.map(Health.down().build(), mock(securityContext))
|
||||
.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
|
||||
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||
Health endpointHealth = endpoint.health();
|
||||
Health extensionHealth = extension.health(mock(Principal.class))
|
||||
.block().getBody();
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
given(securityContext.getPrincipal())
|
||||
.willReturn(mock(Principal.class));
|
||||
Health extensionHealth = extension.health(securityContext).block()
|
||||
.getBody();
|
||||
assertThat(endpointHealth.getDetails())
|
||||
.containsOnlyKeys("application", "first", "second");
|
||||
assertThat(extensionHealth.getDetails())
|
||||
|
|
@ -100,7 +109,8 @@ public class ReactiveHealthEndpointWebExtensionTests {
|
|||
this.contextRunner.run((context) -> {
|
||||
ReactiveHealthEndpointWebExtension extension = context
|
||||
.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) -> {
|
||||
ReactiveHealthEndpointWebExtension extension = context
|
||||
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||
assertThat(extension.health(mock(Principal.class)).block().getBody()
|
||||
.getDetails()).isNotEmpty();
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
given(securityContext.getPrincipal()).willReturn(mock(Principal.class));
|
||||
assertThat(extension.health(securityContext).block().getBody().getDetails())
|
||||
.isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -133,11 +145,60 @@ public class ReactiveHealthEndpointWebExtensionTests {
|
|||
.run((context) -> {
|
||||
ReactiveHealthEndpointWebExtension extension = context
|
||||
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||
assertThat(extension.health(mock(Principal.class)).block().getBody()
|
||||
SecurityContext securityContext = mock(SecurityContext.class);
|
||||
assertThat(extension.health(securityContext).block().getBody()
|
||||
.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
|
||||
static class HealthIndicatorsConfiguration {
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
|
|
@ -30,24 +29,26 @@ import org.springframework.util.Assert;
|
|||
*/
|
||||
public class InvocationContext {
|
||||
|
||||
private final Principal principal;
|
||||
private final SecurityContext securityContext;
|
||||
|
||||
private final Map<String, Object> arguments;
|
||||
|
||||
/**
|
||||
* Creates a new context for an operation being invoked by the given {@code principal}
|
||||
* 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}
|
||||
*/
|
||||
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");
|
||||
this.principal = principal;
|
||||
this.securityContext = securityContext;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
public Principal getPrincipal() {
|
||||
return this.principal;
|
||||
public SecurityContext getSecurityContext() {
|
||||
return this.securityContext;
|
||||
}
|
||||
|
||||
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 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.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
|
||||
|
|
@ -89,7 +90,10 @@ public class ReflectiveOperationInvoker implements OperationInvoker {
|
|||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
@ -102,7 +106,10 @@ public class ReflectiveOperationInvoker implements OperationInvoker {
|
|||
private Object resolveArgument(OperationParameter parameter,
|
||||
InvocationContext context) {
|
||||
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());
|
||||
return this.parameterValueMapper.mapParameterValue(parameter, value);
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public class CachingOperationInvoker implements OperationInvoker {
|
|||
}
|
||||
|
||||
private boolean hasInput(InvocationContext context) {
|
||||
if (context.getPrincipal() != null) {
|
||||
if (context.getSecurityContext().getPrincipal() != null) {
|
||||
return true;
|
||||
}
|
||||
Map<String, Object> arguments = context.getArguments();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
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.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
|
|
@ -97,7 +99,8 @@ public class EndpointMBean implements DynamicMBean {
|
|||
String[] parameterNames = operation.getParameters().stream()
|
||||
.map(JmxOperationParameter::getName).toArray(String[]::new);
|
||||
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) {
|
||||
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.InputStream;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
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.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
|
|
@ -150,7 +152,7 @@ public class JerseyEndpointResourceFactory {
|
|||
arguments.putAll(extractQueryParameters(data));
|
||||
try {
|
||||
Object response = this.operation.invoke(new InvocationContext(
|
||||
data.getSecurityContext().getUserPrincipal(), arguments));
|
||||
new JerseySecurityContext(data.getSecurityContext()), arguments));
|
||||
return convertToJaxRsResponse(response, data.getRequest().getMethod());
|
||||
}
|
||||
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.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
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.InvocationContext;
|
||||
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.web.EndpointMapping;
|
||||
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.HttpStatus;
|
||||
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.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
|
@ -256,33 +262,55 @@ public abstract class AbstractWebFluxEndpointHandlerMapping
|
|||
private static final class ReactiveWebOperationAdapter
|
||||
implements ReactiveWebOperation {
|
||||
|
||||
private static final Principal NO_PRINCIPAL = new Principal() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private final OperationInvoker invoker;
|
||||
|
||||
private final Supplier<Mono<? extends SecurityContext>> securityContextSupplier;
|
||||
|
||||
private ReactiveWebOperationAdapter(OperationInvoker 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
|
||||
public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange,
|
||||
Map<String, String> body) {
|
||||
return exchange.getPrincipal().defaultIfEmpty(NO_PRINCIPAL)
|
||||
.flatMap((principal) -> {
|
||||
Map<String, Object> arguments = getArguments(exchange, body);
|
||||
return handleResult(
|
||||
(Publisher<?>) this.invoker.invoke(new InvocationContext(
|
||||
principal == NO_PRINCIPAL ? null : principal,
|
||||
arguments)),
|
||||
exchange.getRequest().getMethod());
|
||||
});
|
||||
Map<String, Object> arguments = getArguments(exchange, body);
|
||||
return this.securityContextSupplier.get()
|
||||
.map((securityContext) -> new InvocationContext(securityContext,
|
||||
arguments))
|
||||
.flatMap((invocationContext) -> handleResult(
|
||||
(Publisher<?>) this.invoker.invoke(invocationContext),
|
||||
exchange.getRequest().getMethod()));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
|
|
@ -29,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
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.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
|
|
@ -243,7 +245,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping
|
|||
try {
|
||||
return handleResult(
|
||||
this.invoker.invoke(new InvocationContext(
|
||||
request.getUserPrincipal(), arguments)),
|
||||
new ServletSecurityContext(request), arguments)),
|
||||
HttpMethod.valueOf(request.getMethod()));
|
||||
}
|
||||
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;
|
||||
|
||||
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.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link EndpointWebExtension} for the {@link HealthEndpoint}.
|
||||
|
|
@ -39,31 +37,23 @@ public class HealthEndpointWebExtension {
|
|||
|
||||
private final HealthIndicator delegate;
|
||||
|
||||
private final HealthStatusHttpMapper statusHttpMapper;
|
||||
|
||||
private final ShowDetails showDetails;
|
||||
private final HealthWebEndpointResponseMapper responseMapper;
|
||||
|
||||
public HealthEndpointWebExtension(HealthIndicator delegate,
|
||||
HealthStatusHttpMapper statusHttpMapper, ShowDetails showDetails) {
|
||||
HealthWebEndpointResponseMapper responseMapper) {
|
||||
this.delegate = delegate;
|
||||
this.statusHttpMapper = statusHttpMapper;
|
||||
this.showDetails = showDetails;
|
||||
this.responseMapper = responseMapper;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<Health> getHealth(@Nullable Principal principal) {
|
||||
return getHealth(principal, this.showDetails);
|
||||
public WebEndpointResponse<Health> getHealth(SecurityContext securityContext) {
|
||||
return this.responseMapper.map(this.delegate.health(), securityContext);
|
||||
}
|
||||
|
||||
public WebEndpointResponse<Health> getHealth(Principal principal,
|
||||
public WebEndpointResponse<Health> getHealth(SecurityContext securityContext,
|
||||
ShowDetails showDetails) {
|
||||
Health health = this.delegate.health();
|
||||
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
|
||||
if (showDetails == ShowDetails.NEVER
|
||||
|| (showDetails == ShowDetails.WHEN_AUTHENTICATED && principal == null)) {
|
||||
health = Health.status(health.getStatus()).build();
|
||||
}
|
||||
return new WebEndpointResponse<>(health, status);
|
||||
return this.responseMapper.map(this.delegate.health(), securityContext,
|
||||
showDetails);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
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.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Reactive {@link EndpointWebExtension} for the {@link HealthEndpoint}.
|
||||
|
|
@ -36,33 +34,24 @@ public class ReactiveHealthEndpointWebExtension {
|
|||
|
||||
private final ReactiveHealthIndicator delegate;
|
||||
|
||||
private final HealthStatusHttpMapper statusHttpMapper;
|
||||
|
||||
private final ShowDetails showDetails;
|
||||
private final HealthWebEndpointResponseMapper responseMapper;
|
||||
|
||||
public ReactiveHealthEndpointWebExtension(ReactiveHealthIndicator delegate,
|
||||
HealthStatusHttpMapper statusHttpMapper, ShowDetails showDetails) {
|
||||
HealthWebEndpointResponseMapper responseMapper) {
|
||||
this.delegate = delegate;
|
||||
this.statusHttpMapper = statusHttpMapper;
|
||||
this.showDetails = showDetails;
|
||||
this.responseMapper = responseMapper;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public Mono<WebEndpointResponse<Health>> health(@Nullable Principal principal) {
|
||||
return health(principal, this.showDetails);
|
||||
public Mono<WebEndpointResponse<Health>> health(SecurityContext securityContext) {
|
||||
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) {
|
||||
return this.delegate.health().map((health) -> {
|
||||
Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
|
||||
if (showDetails == ShowDetails.NEVER
|
||||
|| (showDetails == ShowDetails.WHEN_AUTHENTICATED
|
||||
&& principal == null)) {
|
||||
health = Health.status(health.getStatus()).build();
|
||||
}
|
||||
return new WebEndpointResponse<>(health, status);
|
||||
});
|
||||
return this.delegate.health().map((health) -> this.responseMapper.map(health,
|
||||
securityContext, showDetails));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ public enum ShowDetails {
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.junit.Test;
|
|||
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
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.OperationInvokerAdvisor;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link DiscoveredOperationsFactory}.
|
||||
|
|
@ -106,7 +108,8 @@ public class DiscoveredOperationsFactoryTests {
|
|||
TestOperation operation = getFirst(
|
||||
this.factory.createOperations("test", new ExampleWithParams()));
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +119,8 @@ public class DiscoveredOperationsFactoryTests {
|
|||
this.invokerAdvisors.add(advisor);
|
||||
TestOperation operation = getFirst(
|
||||
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.getOperationType()).isEqualTo(OperationType.READ);
|
||||
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.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReflectiveOperationInvoker}.
|
||||
|
|
@ -84,8 +86,8 @@ public class ReflectiveOperationInvokerTests {
|
|||
public void invokeShouldInvokeMethod() {
|
||||
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
||||
this.operationMethod, this.parameterValueMapper);
|
||||
Object result = invoker.invoke(
|
||||
new InvocationContext(null, Collections.singletonMap("name", "boot")));
|
||||
Object result = invoker.invoke(new InvocationContext(mock(SecurityContext.class),
|
||||
Collections.singletonMap("name", "boot")));
|
||||
assertThat(result).isEqualTo("toob");
|
||||
}
|
||||
|
||||
|
|
@ -94,8 +96,8 @@ public class ReflectiveOperationInvokerTests {
|
|||
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
||||
this.operationMethod, this.parameterValueMapper);
|
||||
this.thrown.expect(MissingParametersException.class);
|
||||
invoker.invoke(
|
||||
new InvocationContext(null, Collections.singletonMap("name", null)));
|
||||
invoker.invoke(new InvocationContext(mock(SecurityContext.class),
|
||||
Collections.singletonMap("name", null)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -104,8 +106,8 @@ public class ReflectiveOperationInvokerTests {
|
|||
Example.class, "reverseNullable", String.class), OperationType.READ);
|
||||
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
||||
operationMethod, this.parameterValueMapper);
|
||||
Object result = invoker.invoke(
|
||||
new InvocationContext(null, Collections.singletonMap("name", null)));
|
||||
Object result = invoker.invoke(new InvocationContext(mock(SecurityContext.class),
|
||||
Collections.singletonMap("name", null)));
|
||||
assertThat(result).isEqualTo("llun");
|
||||
}
|
||||
|
||||
|
|
@ -113,8 +115,8 @@ public class ReflectiveOperationInvokerTests {
|
|||
public void invokeShouldResolveParameters() {
|
||||
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
|
||||
this.operationMethod, this.parameterValueMapper);
|
||||
Object result = invoker.invoke(
|
||||
new InvocationContext(null, Collections.singletonMap("name", 1234)));
|
||||
Object result = invoker.invoke(new InvocationContext(mock(SecurityContext.class),
|
||||
Collections.singletonMap("name", 1234)));
|
||||
assertThat(result).isEqualTo("4321");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import org.junit.Test;
|
|||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
|
@ -67,7 +68,8 @@ public class CachingOperationInvokerTests {
|
|||
private void assertCacheIsUsed(Map<String, Object> parameters) {
|
||||
OperationInvoker target = mock(OperationInvoker.class);
|
||||
Object expected = new Object();
|
||||
InvocationContext context = new InvocationContext(null, parameters);
|
||||
InvocationContext context = new InvocationContext(mock(SecurityContext.class),
|
||||
parameters);
|
||||
given(target.invoke(context)).willReturn(expected);
|
||||
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
|
||||
Object response = invoker.invoke(context);
|
||||
|
|
@ -84,7 +86,8 @@ public class CachingOperationInvokerTests {
|
|||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("test", "value");
|
||||
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());
|
||||
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L);
|
||||
invoker.invoke(context);
|
||||
|
|
@ -97,7 +100,8 @@ public class CachingOperationInvokerTests {
|
|||
public void targetInvokedWhenCacheExpires() throws InterruptedException {
|
||||
OperationInvoker target = mock(OperationInvoker.class);
|
||||
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());
|
||||
CachingOperationInvoker invoker = new CachingOperationInvoker(target, 50L);
|
||||
invoker.invoke(context);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import java.util.function.Supplier;
|
|||
import org.junit.Test;
|
||||
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.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
|
|
@ -361,6 +362,52 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||
.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 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")
|
||||
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 {
|
||||
|
||||
void write();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
package org.springframework.boot.actuate.endpoint.web.jersey;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
|
|
@ -25,7 +25,6 @@ import javax.servlet.Filter;
|
|||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
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.core.env.Environment;
|
||||
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.web.filter.OncePerRequestFilter;
|
||||
|
||||
|
|
@ -131,7 +135,18 @@ public class JerseyWebEndpointIntegrationTests extends
|
|||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain filterChain)
|
||||
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
|
||||
implements ContextResolver<ObjectMapper> {
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.boot.actuate.endpoint.web.reactive;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
|
@ -40,10 +39,12 @@ import org.springframework.core.env.Environment;
|
|||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
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.reactive.config.EnableWebFlux;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchangeDecorator;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
|
@ -152,8 +153,12 @@ public class WebFluxEndpointIntegrationTests extends
|
|||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange,
|
||||
WebFilterChain chain) {
|
||||
return chain.filter(
|
||||
new MockPrincipalServerWebExchangeDecorator(exchange));
|
||||
return chain.filter(exchange).subscriberContext(
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Test;
|
||||
|
|
@ -48,6 +46,11 @@ import org.springframework.context.annotation.Configuration;
|
|||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpStatus;
|
||||
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.filter.OncePerRequestFilter;
|
||||
|
||||
|
|
@ -145,7 +148,18 @@ public class MvcWebEndpointIntegrationTests extends
|
|||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain filterChain)
|
||||
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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
|
@ -75,7 +77,9 @@ public class HealthEndpointWebIntegrationTests {
|
|||
return new HealthEndpointWebExtension(
|
||||
new CompositeHealthIndicatorFactory().createHealthIndicator(
|
||||
new OrderedHealthAggregator(), healthIndicators),
|
||||
new HealthStatusHttpMapper(), ShowDetails.ALWAYS);
|
||||
new HealthWebEndpointResponseMapper(new HealthStatusHttpMapper(),
|
||||
ShowDetails.ALWAYS,
|
||||
new HashSet<>(Arrays.asList("ACTUATOR"))));
|
||||
}
|
||||
|
||||
@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])
|
||||
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.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])
|
||||
management.endpoint.heapdump.cache.time-to-live=0ms # Maximum time that a response can be cached.
|
||||
|
|
|
|||
|
|
@ -523,14 +523,18 @@ following values:
|
|||
|`never`
|
||||
|Details are never shown.
|
||||
|
||||
|`when-authenticated`
|
||||
|Details are only shown to authenticated users.
|
||||
|`when-authorized`
|
||||
|Details are only shown to authorized users. Authorized roles can be configured using
|
||||
`management.endpoint.health.roles`.
|
||||
|
||||
|`always`
|
||||
|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
|
||||
configuration must permit access to the health endpoint for both authenticated and
|
||||
|
|
|
|||
Loading…
Reference in New Issue