Add actuator specific ObjectMapper
Prior to this commit, Actuator endpoints would use the application ObjectMapper instance for serializing payloads as JSON. This was problematic in several cases: * application-specific configuration would change the actuator endpoint output. * choosing a different JSON mapper implementation in the application would break completely some endpoints. Spring Boot Actuator already has a hard dependency on Jackson, and this commit uses that fact to configure a shared `ObjectMapper` instance that will be used by the Actuator infrastructure consistently, without polluting the application context. This `ObjectMapper` is used in Actuator for: * JMX endpoints * Spring MVC endpoints with an HTTP message converter * Spring WebFlux endpoints with an `Encoder` * Jersey endpoints with a `ContextResolver<ObjectMapper>` For all web endpoints, this configuration is limited to the actuator-specific media types such as `"application/vnd.spring-boot.actuator.v3+json"`. Fixes gh-12951
This commit is contained in:
parent
420af17570
commit
97af0b2f3a
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -25,6 +25,7 @@ import org.springframework.boot.actuate.endpoint.annotation.EndpointConverter;
|
|||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.json.ActuatorJsonMapperProvider;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.convert.ApplicationConversionService;
|
||||
|
@ -75,4 +76,9 @@ public class EndpointAutoConfiguration {
|
|||
return new CachingOperationInvokerAdvisor(new EndpointIdTimeToLivePropertyFunction(environment));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ActuatorJsonMapperProvider actuatorJsonMapperProvider() {
|
||||
return new ActuatorJsonMapperProvider();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -20,8 +20,6 @@ import java.util.stream.Collectors;
|
|||
|
||||
import javax.management.MBeanServer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
|
@ -35,6 +33,7 @@ import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointExporter;
|
|||
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointsSupplier;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperationResponseMapper;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.json.ActuatorJsonMapperProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
|
@ -85,12 +84,12 @@ public class JmxEndpointAutoConfiguration {
|
|||
@Bean
|
||||
@ConditionalOnSingleCandidate(MBeanServer.class)
|
||||
public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer, Environment environment,
|
||||
ObjectProvider<ObjectMapper> objectMapper, JmxEndpointsSupplier jmxEndpointsSupplier) {
|
||||
ActuatorJsonMapperProvider actuatorJsonMapperProvider, JmxEndpointsSupplier jmxEndpointsSupplier) {
|
||||
String contextId = ObjectUtils.getIdentityHexString(this.applicationContext);
|
||||
EndpointObjectNameFactory objectNameFactory = new DefaultEndpointObjectNameFactory(this.properties, environment,
|
||||
mBeanServer, contextId);
|
||||
JmxOperationResponseMapper responseMapper = new JacksonJmxOperationResponseMapper(
|
||||
objectMapper.getIfAvailable());
|
||||
actuatorJsonMapperProvider.getInstance());
|
||||
return new JmxEndpointExporter(mBeanServer, objectNameFactory, responseMapper,
|
||||
jmxEndpointsSupplier.getEndpoints());
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -22,7 +22,10 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.ext.ContextResolver;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
|
||||
|
@ -32,6 +35,8 @@ import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfi
|
|||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.json.ActuatorJsonMapperProvider;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
|
@ -84,6 +89,12 @@ class JerseyWebEndpointManagementContextConfiguration {
|
|||
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ResourceConfigCustomizer actuatorResourceConfigCustomizer(ActuatorJsonMapperProvider jsonMapperProvider) {
|
||||
return (ResourceConfig config) -> config.register(
|
||||
new ActuatorJsonMapperContextResolver(jsonMapperProvider.getInstance()), ContextResolver.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register endpoints with the {@link ResourceConfig}. The
|
||||
* {@link ResourceConfigCustomizer} cannot be used because we don't want to apply
|
||||
|
@ -145,4 +156,20 @@ class JerseyWebEndpointManagementContextConfiguration {
|
|||
|
||||
}
|
||||
|
||||
@Produces({ ActuatorMediaType.V3_JSON, ActuatorMediaType.V2_JSON })
|
||||
private static final class ActuatorJsonMapperContextResolver implements ContextResolver<ObjectMapper> {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private ActuatorJsonMapperContextResolver(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectMapper getContext(Class<?> type) {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -26,6 +26,8 @@ import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfi
|
|||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.json.ActuatorJsonMapperProvider;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
|
@ -40,8 +42,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.codec.CodecCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.CodecConfigurer;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.DispatcherHandler;
|
||||
|
@ -52,6 +59,7 @@ import org.springframework.web.reactive.DispatcherHandler;
|
|||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @author Brian Clozel
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ManagementContextConfiguration(proxyBeanMethods = false)
|
||||
|
@ -93,4 +101,16 @@ public class WebFluxEndpointManagementContextConfiguration {
|
|||
corsProperties.toCorsConfiguration());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(-1)
|
||||
public CodecCustomizer actuatorJsonCodec(ActuatorJsonMapperProvider actuatorJsonMapperProvider) {
|
||||
return (configurer) -> {
|
||||
MediaType v3MediaType = MediaType.parseMediaType(ActuatorMediaType.V3_JSON);
|
||||
MediaType v2MediaType = MediaType.parseMediaType(ActuatorMediaType.V2_JSON);
|
||||
CodecConfigurer.CustomCodecs customCodecs = configurer.customCodecs();
|
||||
customCodecs.register(
|
||||
new Jackson2JsonEncoder(actuatorJsonMapperProvider.getInstance(), v3MediaType, v2MediaType));
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -20,12 +20,16 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.json.ActuatorJsonMapperProvider;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
|
@ -42,9 +46,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for Spring MVC
|
||||
|
@ -91,4 +101,35 @@ public class WebMvcEndpointManagementContextConfiguration {
|
|||
corsProperties.toCorsConfiguration());
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public static class JsonWebMvcConfigurer implements WebMvcConfigurer, Ordered {
|
||||
|
||||
private final ActuatorJsonMapperProvider actuatorJsonMapperProvider;
|
||||
|
||||
public JsonWebMvcConfigurer(ActuatorJsonMapperProvider objectMapperFactory) {
|
||||
this.actuatorJsonMapperProvider = objectMapperFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
converters.add(new ActuatorJsonHttpMessageConverter(this.actuatorJsonMapperProvider.getInstance()));
|
||||
}
|
||||
|
||||
// WebMvcAutoConfiguration is ordered at 0
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ActuatorJsonHttpMessageConverter extends AbstractJackson2HttpMessageConverter {
|
||||
|
||||
ActuatorJsonHttpMessageConverter(ObjectMapper objectMapper) {
|
||||
super(objectMapper, MediaType.parseMediaType(ActuatorMediaType.V3_JSON),
|
||||
MediaType.parseMediaType(ActuatorMediaType.V2_JSON));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -16,9 +16,13 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.web.servlet;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
|
||||
import org.springframework.boot.actuate.endpoint.json.ActuatorJsonMapperProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
|
@ -33,11 +37,15 @@ import org.springframework.boot.web.servlet.error.ErrorAttributes;
|
|||
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
|
||||
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.context.request.RequestContextListener;
|
||||
import org.springframework.web.filter.RequestContextFilter;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* {@link ManagementContextConfiguration @ManagementContextConfiguration} for Spring MVC
|
||||
|
@ -108,6 +116,29 @@ class WebMvcEndpointChildContextConfiguration {
|
|||
return new OrderedRequestContextFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Since {@code WebMvcEndpointManagementContextConfiguration} is adding an
|
||||
* actuator-specific JSON message converter, {@code @EnableWebMvc} will not register
|
||||
* default converters. We need to register a JSON converter for plain
|
||||
* {@code "application/json"} still.
|
||||
* WebMvcEndpointChildContextConfigurationIntegrationTests
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public static class FallbackJsonConverterConfigurer implements WebMvcConfigurer {
|
||||
|
||||
private final ActuatorJsonMapperProvider actuatorJsonMapperProvider;
|
||||
|
||||
FallbackJsonConverterConfigurer(ObjectProvider<ActuatorJsonMapperProvider> objectMapperSupplier) {
|
||||
this.actuatorJsonMapperProvider = objectMapperSupplier.getIfAvailable(ActuatorJsonMapperProvider::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
converters.add(new MappingJackson2HttpMessageConverter(this.actuatorJsonMapperProvider.getInstance()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link WebServerFactoryCustomizer} to add an {@link ErrorPage} so that the
|
||||
* {@link ManagementErrorEndpoint} can be used.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -52,7 +52,7 @@ class ScheduledTasksEndpointDocumentationTests extends MockMvcEndpointDocumentat
|
|||
|
||||
@Test
|
||||
void scheduledTasks() throws Exception {
|
||||
this.mockMvc.perform(get("/actuator/scheduledtasks")).andExpect(status().isOk())
|
||||
this.mockMvc.perform(get("/actuator/scheduledtasks").accept("application/json")).andExpect(status().isOk())
|
||||
.andDo(document("scheduled-tasks",
|
||||
preprocessResponse(replacePattern(
|
||||
Pattern.compile("org.*\\.ScheduledTasksEndpointDocumentationTests\\$TestConfiguration"),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -21,6 +21,7 @@ import java.util.Collections;
|
|||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration.JerseyWebEndpointsResourcesRegistrar;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.jersey.JerseySameManagementContextConfiguration;
|
||||
|
@ -41,8 +42,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
class JerseyWebEndpointManagementContextConfigurationTests {
|
||||
|
||||
private final WebApplicationContextRunner runner = new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebEndpointAutoConfiguration.class,
|
||||
JerseyWebEndpointManagementContextConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class,
|
||||
WebEndpointAutoConfiguration.class, JerseyWebEndpointManagementContextConfiguration.class))
|
||||
.withBean(WebEndpointsSupplier.class, () -> Collections::emptyList);
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -20,7 +20,6 @@ import java.util.function.Supplier;
|
|||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration;
|
||||
|
@ -33,32 +32,23 @@ import org.springframework.boot.actuate.endpoint.web.EndpointServlet;
|
|||
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.test.context.TestSecurityContextHolder;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
|
||||
|
||||
import static org.hamcrest.Matchers.both;
|
||||
import static org.hamcrest.Matchers.hasKey;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
@ -67,98 +57,39 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
* Integration tests for the Actuator's MVC endpoints.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class WebMvcEndpointIntegrationTests {
|
||||
public class WebMvcEndpointIntegrationTests {
|
||||
|
||||
private AnnotationConfigServletWebApplicationContext context;
|
||||
|
||||
@AfterEach
|
||||
void close() {
|
||||
TestSecurityContextHolder.clearContext();
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void endpointsAreSecureByDefault() throws Exception {
|
||||
this.context = new AnnotationConfigServletWebApplicationContext();
|
||||
this.context.register(SecureConfiguration.class);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/actuator/beans").accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void endpointsAreSecureByDefaultWithCustomBasePath() throws Exception {
|
||||
this.context = new AnnotationConfigServletWebApplicationContext();
|
||||
this.context.register(SecureConfiguration.class);
|
||||
TestPropertyValues.of("management.endpoints.web.base-path:/management").applyTo(this.context);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/management/beans").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void endpointsAreSecureWithActuatorRoleWithCustomBasePath() throws Exception {
|
||||
TestSecurityContextHolder.getContext()
|
||||
.setAuthentication(new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR"));
|
||||
this.context = new AnnotationConfigServletWebApplicationContext();
|
||||
this.context.register(SecureConfiguration.class);
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.base-path:/management", "management.endpoints.web.exposure.include=*")
|
||||
.applyTo(this.context);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/management/beans")).andExpect(status().isOk());
|
||||
}
|
||||
private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, GsonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class,
|
||||
WebEndpointAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
|
||||
AuditAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
|
||||
WebMvcAutoConfiguration.class, ManagementContextAutoConfiguration.class,
|
||||
AuditAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
|
||||
BeansEndpointAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void linksAreProvidedToAllEndpointTypes() throws Exception {
|
||||
this.context = new AnnotationConfigServletWebApplicationContext();
|
||||
this.context.register(DefaultConfiguration.class, EndpointsConfiguration.class);
|
||||
TestPropertyValues.of("management.endpoints.web.exposure.include=*").applyTo(this.context);
|
||||
MockMvc mockMvc = doCreateMockMvc();
|
||||
mockMvc.perform(get("/actuator").accept("*/*")).andExpect(status().isOk()).andExpect(jsonPath("_links",
|
||||
both(hasKey("beans")).and(hasKey("servlet")).and(hasKey("restcontroller")).and(hasKey("controller"))));
|
||||
this.contextRunner.withUserConfiguration(EndpointsConfiguration.class)
|
||||
.withPropertyValues("management.endpoints.web.exposure.include=*").run((context) -> {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
|
||||
mockMvc.perform(get("/actuator").accept("*/*")).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("_links", both(hasKey("beans")).and(hasKey("servlet"))
|
||||
.and(hasKey("restcontroller")).and(hasKey("controller"))));
|
||||
});
|
||||
}
|
||||
|
||||
private MockMvc createSecureMockMvc() {
|
||||
return doCreateMockMvc(springSecurity());
|
||||
}
|
||||
|
||||
private MockMvc doCreateMockMvc(MockMvcConfigurer... configurers) {
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.refresh();
|
||||
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.context);
|
||||
for (MockMvcConfigurer configurer : configurers) {
|
||||
builder.apply(configurer);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ImportAutoConfiguration({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
|
||||
ServletManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,
|
||||
DispatcherServletAutoConfiguration.class, BeansEndpointAutoConfiguration.class })
|
||||
static class DefaultConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Import(SecureConfiguration.class)
|
||||
@ImportAutoConfiguration({ HypermediaAutoConfiguration.class })
|
||||
static class SpringHateoasConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Import(SecureConfiguration.class)
|
||||
@ImportAutoConfiguration({ HypermediaAutoConfiguration.class, RepositoryRestMvcAutoConfiguration.class })
|
||||
static class SpringDataRestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Import(DefaultConfiguration.class)
|
||||
@ImportAutoConfiguration({ SecurityAutoConfiguration.class })
|
||||
static class SecureConfiguration {
|
||||
|
||||
@Test
|
||||
void dedicatedJsonMapperIsUsed() throws Exception {
|
||||
this.contextRunner.withPropertyValues("spring.mvc.converters.preferred-json-mapper:gson",
|
||||
"management.endpoints.web.exposure.include=*").run((context) -> {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
|
||||
mockMvc.perform(get("/actuator/beans").accept("*/*")).andExpect(status().isOk())
|
||||
.andExpect(MockMvcResultMatchers.header().string("Content-Type",
|
||||
startsWith("application/vnd.spring-boot.actuator")));
|
||||
});
|
||||
}
|
||||
|
||||
@ServletEndpoint(id = "servlet")
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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
|
||||
*
|
||||
* https://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.autoconfigure.integrationtest;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.test.context.TestSecurityContextHolder;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Integration tests for the Actuator's MVC endpoints security features.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class WebMvcEndpointSecurityIntegrationTests {
|
||||
|
||||
private AnnotationConfigServletWebApplicationContext context;
|
||||
|
||||
@AfterEach
|
||||
void close() {
|
||||
TestSecurityContextHolder.clearContext();
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void endpointsAreSecureByDefault() throws Exception {
|
||||
this.context = new AnnotationConfigServletWebApplicationContext();
|
||||
this.context.register(SecureConfiguration.class);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/actuator/beans").accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void endpointsAreSecureByDefaultWithCustomBasePath() throws Exception {
|
||||
this.context = new AnnotationConfigServletWebApplicationContext();
|
||||
this.context.register(SecureConfiguration.class);
|
||||
TestPropertyValues.of("management.endpoints.web.base-path:/management").applyTo(this.context);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/management/beans").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void endpointsAreSecureWithActuatorRoleWithCustomBasePath() throws Exception {
|
||||
TestSecurityContextHolder.getContext()
|
||||
.setAuthentication(new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR"));
|
||||
this.context = new AnnotationConfigServletWebApplicationContext();
|
||||
this.context.register(SecureConfiguration.class);
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.base-path:/management", "management.endpoints.web.exposure.include=*")
|
||||
.applyTo(this.context);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/management/beans")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
private MockMvc createSecureMockMvc() {
|
||||
return doCreateMockMvc(springSecurity());
|
||||
}
|
||||
|
||||
private MockMvc doCreateMockMvc(MockMvcConfigurer... configurers) {
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.refresh();
|
||||
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.context);
|
||||
for (MockMvcConfigurer configurer : configurers) {
|
||||
builder.apply(configurer);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ImportAutoConfiguration({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
|
||||
ServletManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,
|
||||
DispatcherServletAutoConfiguration.class, BeansEndpointAutoConfiguration.class })
|
||||
static class DefaultConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Import(DefaultConfiguration.class)
|
||||
@ImportAutoConfiguration({ SecurityAutoConfiguration.class })
|
||||
static class SecureConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -59,6 +59,7 @@ class WebMvcEndpointChildContextConfigurationIntegrationTests {
|
|||
WebClient client = WebClient.create("http://localhost:" + port);
|
||||
ClientResponse response = client.get().uri("actuator/fail").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange().block();
|
||||
assertThat(response.headers().contentType().get()).isEqualTo(MediaType.APPLICATION_JSON);
|
||||
assertThat(response.bodyToMono(String.class).block()).contains("message\":\"Epic Fail");
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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
|
||||
*
|
||||
* https://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.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
/**
|
||||
* Factory for an {@code ObjectMapper} instance to be shared within Actuator
|
||||
* infrastructure.
|
||||
*
|
||||
* <p>
|
||||
* The goal is to have a Jackson configuration separate from the rest of the application
|
||||
* to keep a consistent serialization behavior between applications.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class ActuatorJsonMapperProvider {
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public ObjectMapper getInstance() {
|
||||
if (this.objectMapper == null) {
|
||||
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
|
||||
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
builder.featuresToDisable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS);
|
||||
this.objectMapper = builder.build();
|
||||
}
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* JSON support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.json;
|
|
@ -46,6 +46,9 @@
|
|||
<disallow pkg="org.springframework.web.servlet" />
|
||||
</subpackage>
|
||||
</subpackage>
|
||||
<subpackage name="json">
|
||||
<allow pkg="org.springframework.http.converter" />
|
||||
</subpackage>
|
||||
</subpackage>
|
||||
|
||||
<!-- Logging -->
|
||||
|
|
Loading…
Reference in New Issue