From a6ecd55f1ffcea6467facfbea646012db7a2ed93 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 9 Feb 2018 11:23:47 +0000 Subject: [PATCH] Make expose and exclude properties affect servlet endpoints Closes gh-11968 --- ...ndpointManagementContextConfiguration.java | 14 ++ ...ebMvcEndpointExposureIntegrationTests.java | 193 +++++++++++------- 2 files changed, 132 insertions(+), 75 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java index 8a3607eaae..299a7d014f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java @@ -16,7 +16,11 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web; +import java.util.Set; + +import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration; +import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint; import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar; import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; @@ -28,6 +32,7 @@ import org.springframework.context.annotation.Configuration; * {@link ManagementContextConfiguration} for servlet endpoints. * * @author Phillip Webb + * @author Andy Wilkinson * * @since 2.0.0 */ @@ -43,4 +48,13 @@ public class ServletEndpointManagementContextConfiguration { servletEndpointsSupplier.getEndpoints()); } + @Bean + public ExposeExcludePropertyEndpointFilter servletExposeExcludePropertyEndpointFilter( + WebEndpointProperties properties) { + Set expose = properties.getExpose(); + Set exclude = properties.getExclude(); + return new ExposeExcludePropertyEndpointFilter<>(ExposableServletEndpoint.class, + expose, exclude); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java index fddf8ad8be..97eae145ed 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java @@ -16,6 +16,14 @@ package org.springframework.boot.actuate.autoconfigure.integrationtest; +import java.io.IOException; +import java.util.function.Supplier; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.junit.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; @@ -23,23 +31,26 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAu import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.trace.HttpTraceAutoConfiguration; +import org.springframework.boot.actuate.endpoint.web.EndpointServlet; import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; +import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.reactive.server.EntityExchangeResult; +import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; /** * Integration tests for Endpoints over Spring MVC. @@ -49,38 +60,42 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder */ public class WebMvcEndpointExposureIntegrationTests { - private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - ServletWebServerFactoryAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, - JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class, - WebMvcAutoConfiguration.class, EndpointAutoConfiguration.class, - WebEndpointAutoConfiguration.class, - ManagementContextAutoConfiguration.class, - ServletManagementContextAutoConfiguration.class, - ManagementContextAutoConfiguration.class, - ServletManagementContextAutoConfiguration.class, - HttpTraceAutoConfiguration.class)) - .withConfiguration( - AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)) - .withUserConfiguration(CustomMvcEndpoint.class); + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner( + AnnotationConfigServletWebServerApplicationContext::new).withConfiguration( + AutoConfigurations.of(ServletWebServerFactoryAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, + JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + WebMvcAutoConfiguration.class, + EndpointAutoConfiguration.class, + WebEndpointAutoConfiguration.class, + ManagementContextAutoConfiguration.class, + ServletManagementContextAutoConfiguration.class, + ManagementContextAutoConfiguration.class, + ServletManagementContextAutoConfiguration.class, + HttpTraceAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)) + .withUserConfiguration(CustomMvcEndpoint.class, + CustomServletEndpoint.class) + .withPropertyValues("server.port:0"); @Test public void webEndpointsAreDisabledByDefault() { this.contextRunner.run((context) -> { - MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build(); - assertThat(isExposed(mvc, HttpMethod.GET, "beans")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "conditions")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "configprops")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "custommvc")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "env")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "health")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "info")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "mappings")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.POST, "shutdown")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "threaddump")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "httptrace")).isFalse(); + WebTestClient client = createClient(context); + assertThat(isExposed(client, HttpMethod.GET, "beans")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "conditions")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "configprops")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "custommvc")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "customservlet")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "env")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "health")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "info")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "mappings")).isFalse(); + assertThat(isExposed(client, HttpMethod.POST, "shutdown")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "threaddump")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "httptrace")).isFalse(); }); } @@ -89,18 +104,19 @@ public class WebMvcEndpointExposureIntegrationTests { WebApplicationContextRunner contextRunner = this.contextRunner .withPropertyValues("management.endpoints.web.expose=*"); contextRunner.run((context) -> { - MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build(); - assertThat(isExposed(mvc, HttpMethod.GET, "beans")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "conditions")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "configprops")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "custommvc")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "env")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "health")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "info")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "mappings")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.POST, "shutdown")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "threaddump")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "httptrace")).isTrue(); + WebTestClient client = createClient(context); + assertThat(isExposed(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "conditions")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "configprops")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "custommvc")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "customservlet")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "env")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "health")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "info")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "mappings")).isTrue(); + assertThat(isExposed(client, HttpMethod.POST, "shutdown")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "threaddump")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "httptrace")).isTrue(); }); } @@ -109,18 +125,19 @@ public class WebMvcEndpointExposureIntegrationTests { WebApplicationContextRunner contextRunner = this.contextRunner .withPropertyValues("management.endpoints.web.expose=beans"); contextRunner.run((context) -> { - MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build(); - assertThat(isExposed(mvc, HttpMethod.GET, "beans")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "conditions")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "configprops")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "custommvc")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "env")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "health")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "info")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "mappings")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.POST, "shutdown")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "threaddump")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "httptrace")).isFalse(); + WebTestClient client = createClient(context); + assertThat(isExposed(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "conditions")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "configprops")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "custommvc")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "customservlet")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "env")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "health")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "info")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "mappings")).isFalse(); + assertThat(isExposed(client, HttpMethod.POST, "shutdown")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "threaddump")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "httptrace")).isFalse(); }); } @@ -130,34 +147,43 @@ public class WebMvcEndpointExposureIntegrationTests { "management.endpoints.web.expose=*", "management.endpoints.web.exclude=shutdown"); contextRunner.run((context) -> { - MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build(); - assertThat(isExposed(mvc, HttpMethod.GET, "beans")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "conditions")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "configprops")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "custommvc")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "env")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "health")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "info")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "mappings")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.POST, "shutdown")).isFalse(); - assertThat(isExposed(mvc, HttpMethod.GET, "threaddump")).isTrue(); - assertThat(isExposed(mvc, HttpMethod.GET, "httptrace")).isTrue(); + WebTestClient client = createClient(context); + assertThat(isExposed(client, HttpMethod.GET, "beans")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "conditions")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "configprops")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "custommvc")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "customservlet")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "env")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "health")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "info")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "mappings")).isTrue(); + assertThat(isExposed(client, HttpMethod.POST, "shutdown")).isFalse(); + assertThat(isExposed(client, HttpMethod.GET, "threaddump")).isTrue(); + assertThat(isExposed(client, HttpMethod.GET, "httptrace")).isTrue(); }); } - private boolean isExposed(MockMvc mockMvc, HttpMethod method, String path) + private WebTestClient createClient(AssertableWebApplicationContext context) { + int port = context + .getSourceApplicationContext(ServletWebServerApplicationContext.class) + .getWebServer().getPort(); + return WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build(); + } + + private boolean isExposed(WebTestClient client, HttpMethod method, String path) throws Exception { path = "/actuator/" + path; - MvcResult mvcResult = mockMvc.perform(request(method, path)).andReturn(); - int status = mvcResult.getResponse().getStatus(); - if (status == HttpStatus.OK.value()) { + EntityExchangeResult result = client.method(method).uri(path).exchange() + .expectBody().returnResult(); + if (result.getStatus() == HttpStatus.OK) { return true; } - if (status == HttpStatus.NOT_FOUND.value()) { + if (result.getStatus() == HttpStatus.NOT_FOUND) { return false; } - throw new IllegalStateException(String - .format("Unexpected %s HTTP status for " + "endpoint %s", status, path)); + throw new IllegalStateException( + String.format("Unexpected %s HTTP status for " + "endpoint %s", + result.getStatus(), path)); } @RestControllerEndpoint(id = "custommvc") @@ -170,4 +196,21 @@ public class WebMvcEndpointExposureIntegrationTests { } + @ServletEndpoint(id = "customservlet") + static class CustomServletEndpoint implements Supplier { + + @Override + public EndpointServlet get() { + return new EndpointServlet(new HttpServlet() { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + } + + }); + } + + } + }