EndpointRequest should consider server.servlet.path

Fixes gh-12934
This commit is contained in:
Madhura Bhave
2018-04-25 14:23:31 -07:00
committed by Phillip Webb
parent 5b3cb8a698
commit 6855c5556d
7 changed files with 133 additions and 26 deletions

View File

@@ -33,6 +33,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider;
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -40,6 +41,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
/**
* Factory that can be used to create a {@link RequestMatcher} for actuator endpoint
@@ -114,7 +116,7 @@ public final class EndpointRequest {
* The request matcher used to match against {@link Endpoint actuator endpoints}.
*/
public static final class EndpointRequestMatcher
extends ApplicationContextRequestMatcher<PathMappedEndpoints> {
extends ApplicationContextRequestMatcher<WebApplicationContext> {
private final List<Object> includes;
@@ -140,7 +142,7 @@ public final class EndpointRequest {
private EndpointRequestMatcher(List<Object> includes, List<Object> excludes,
boolean includeLinks) {
super(PathMappedEndpoints.class);
super(WebApplicationContext.class);
this.includes = includes;
this.excludes = excludes;
this.includeLinks = includeLinks;
@@ -163,32 +165,35 @@ public final class EndpointRequest {
}
@Override
protected void initialized(Supplier<PathMappedEndpoints> pathMappedEndpoints) {
this.delegate = createDelegate(pathMappedEndpoints);
protected void initialized(Supplier<WebApplicationContext> webApplicationContext) {
this.delegate = createDelegate(webApplicationContext);
}
private RequestMatcher createDelegate(
Supplier<PathMappedEndpoints> pathMappedEndpoints) {
Supplier<WebApplicationContext> webApplicationContext) {
try {
return createDelegate(pathMappedEndpoints.get());
WebApplicationContext context = webApplicationContext.get();
PathMappedEndpoints pathMappedEndpoints = context.getBean(PathMappedEndpoints.class);
DispatcherServletPathProvider pathProvider = context.getBean(DispatcherServletPathProvider.class);
return createDelegate(pathMappedEndpoints, pathProvider.getServletPath());
}
catch (NoSuchBeanDefinitionException ex) {
return EMPTY_MATCHER;
}
}
private RequestMatcher createDelegate(PathMappedEndpoints pathMappedEndpoints) {
private RequestMatcher createDelegate(PathMappedEndpoints pathMappedEndpoints, String servletPath) {
Set<String> paths = new LinkedHashSet<>();
if (this.includes.isEmpty()) {
paths.addAll(pathMappedEndpoints.getAllPaths());
}
streamPaths(this.includes, pathMappedEndpoints).forEach(paths::add);
streamPaths(this.excludes, pathMappedEndpoints).forEach(paths::remove);
List<RequestMatcher> delegateMatchers = getDelegateMatchers(paths);
List<RequestMatcher> delegateMatchers = getDelegateMatchers(servletPath, paths);
if (this.includeLinks
&& StringUtils.hasText(pathMappedEndpoints.getBasePath())) {
delegateMatchers.add(
new AntPathRequestMatcher(pathMappedEndpoints.getBasePath()));
new AntPathRequestMatcher(servletPath + pathMappedEndpoints.getBasePath()));
}
return new OrRequestMatcher(delegateMatchers);
}
@@ -216,14 +221,14 @@ public final class EndpointRequest {
return annotation.id();
}
private List<RequestMatcher> getDelegateMatchers(Set<String> paths) {
return paths.stream().map((path) -> new AntPathRequestMatcher(path + "/**"))
private List<RequestMatcher> getDelegateMatchers(String servletPath, Set<String> paths) {
return paths.stream().map((path) -> new AntPathRequestMatcher(servletPath + path + "/**"))
.collect(Collectors.toList());
}
@Override
protected boolean matches(HttpServletRequest request,
Supplier<PathMappedEndpoints> context) {
Supplier<WebApplicationContext> context) {
return this.delegate.matches(request);
}
@@ -233,29 +238,37 @@ public final class EndpointRequest {
* The request matcher used to match against the links endpoint.
*/
public static final class LinksRequestMatcher
extends ApplicationContextRequestMatcher<WebEndpointProperties> {
extends ApplicationContextRequestMatcher<WebApplicationContext> {
private RequestMatcher delegate;
private LinksRequestMatcher() {
super(WebEndpointProperties.class);
super(WebApplicationContext.class);
}
@Override
protected void initialized(Supplier<WebEndpointProperties> properties) {
this.delegate = createDelegate(properties.get());
protected void initialized(Supplier<WebApplicationContext> webApplicationContext) {
try {
WebApplicationContext context = webApplicationContext.get();
WebEndpointProperties properties = context.getBean(WebEndpointProperties.class);
DispatcherServletPathProvider pathProvider = context.getBean(DispatcherServletPathProvider.class);
this.delegate = createDelegate(pathProvider.getServletPath(), properties);
}
catch (NoSuchBeanDefinitionException ex) {
this.delegate = EMPTY_MATCHER;
}
}
private RequestMatcher createDelegate(WebEndpointProperties properties) {
private RequestMatcher createDelegate(String path, WebEndpointProperties properties) {
if (StringUtils.hasText(properties.getBasePath())) {
return new AntPathRequestMatcher(properties.getBasePath());
return new AntPathRequestMatcher(path + properties.getBasePath());
}
return EMPTY_MATCHER;
}
@Override
protected boolean matches(HttpServletRequest request,
Supplier<WebEndpointProperties> context) {
Supplier<WebApplicationContext> context) {
return this.delegate.matches(request);
}

View File

@@ -25,6 +25,7 @@ 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.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
import org.springframework.context.annotation.Bean;
@@ -92,4 +93,9 @@ class WebMvcEndpointChildContextConfiguration {
return new OrderedRequestContextFilter();
}
@Bean
public DispatcherServletPathProvider childDispatcherServletPathProvider() {
return () -> "";
}
}

View File

@@ -30,6 +30,7 @@ import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -71,6 +72,16 @@ public class EndpointRequestTests {
assertMatcher(matcher).doesNotMatch("/actuator/baz");
}
@Test
public void toAnyEndpointWhenServletPathNotEmptyShouldMatch() {
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
assertMatcher(matcher, "/actuator", "/spring").matches("/spring", "/actuator/foo");
assertMatcher(matcher, "/actuator", "/spring").matches("/spring", "/actuator/bar");
assertMatcher(matcher, "/actuator", "/spring").matches("/spring", "/actuator");
assertMatcher(matcher, "/actuator", "/spring").doesNotMatch("/spring", "/actuator/baz");
assertMatcher(matcher, "/actuator", "/spring").doesNotMatch("", "/actuator/foo");
}
@Test
public void toEndpointClassShouldMatchEndpointPath() {
RequestMatcher matcher = EndpointRequest.to(FooEndpoint.class);
@@ -114,6 +125,13 @@ public class EndpointRequestTests {
assertMatcher.doesNotMatch("/");
}
@Test
public void toLinksWhenServletPathNotEmptyShouldNotMatch() {
RequestMatcher matcher = EndpointRequest.toLinks();
RequestMatcherAssert assertMatcher = assertMatcher(matcher, "/actuator", "/spring");
assertMatcher.matches("/spring/actuator");
}
@Test
public void excludeByClassShouldNotMatchExcluded() {
RequestMatcher matcher = EndpointRequest.toAnyEndpoint()
@@ -179,6 +197,10 @@ public class EndpointRequestTests {
return assertMatcher(matcher, mockPathMappedEndpoints(basePath));
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String basePath, String servletPath) {
return assertMatcher(matcher, mockPathMappedEndpoints(basePath), servletPath);
}
private PathMappedEndpoints mockPathMappedEndpoints(String basePath) {
List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
endpoints.add(mockEndpoint("foo", "foo"));
@@ -195,6 +217,11 @@ public class EndpointRequestTests {
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
PathMappedEndpoints pathMappedEndpoints) {
return assertMatcher(matcher, pathMappedEndpoints, "");
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
PathMappedEndpoints pathMappedEndpoints, String servletPath) {
StaticWebApplicationContext context = new StaticWebApplicationContext();
context.registerBean(WebEndpointProperties.class);
if (pathMappedEndpoints != null) {
@@ -205,6 +232,7 @@ public class EndpointRequestTests {
properties.setBasePath(pathMappedEndpoints.getBasePath());
}
}
context.registerBean(DispatcherServletPathProvider.class, () -> () -> servletPath);
return assertThat(new RequestMatcherAssert(context, matcher));
}
@@ -219,8 +247,12 @@ public class EndpointRequestTests {
this.matcher = matcher;
}
public void matches(String path) {
matches(mockRequest(path));
public void matches(String servletPath) {
matches(mockRequest(servletPath));
}
public void matches(String servletPath, String pathInfo) {
matches(mockRequest(servletPath, pathInfo));
}
private void matches(HttpServletRequest request) {
@@ -228,8 +260,12 @@ public class EndpointRequestTests {
.as("Matches " + getRequestPath(request)).isTrue();
}
public void doesNotMatch(String path) {
doesNotMatch(mockRequest(path));
public void doesNotMatch(String servletPath) {
doesNotMatch(mockRequest(servletPath));
}
public void doesNotMatch(String servletPath, String pathInfo) {
doesNotMatch(mockRequest(servletPath, pathInfo));
}
private void doesNotMatch(HttpServletRequest request) {
@@ -237,8 +273,8 @@ public class EndpointRequestTests {
.as("Does not match " + getRequestPath(request)).isFalse();
}
private MockHttpServletRequest mockRequest(String path) {
return mockRequest(null, path);
private MockHttpServletRequest mockRequest(String servletPath) {
return mockRequest(servletPath, null);
}
private MockHttpServletRequest mockRequest(String servletPath, String path) {

View File

@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.web.servlet;
import org.junit.Test;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
import org.springframework.context.annotation.Bean;
@@ -62,6 +63,13 @@ public class WebMvcEndpointChildContextConfigurationTests {
});
}
@Test
public void contextShouldConfigureDispatcherServletPathProviderWithEmptyPath() {
this.contextRunner.withUserConfiguration(WebMvcEndpointChildContextConfiguration.class)
.run((context) -> assertThat(context.getBean(DispatcherServletPathProvider.class)
.getServletPath()).isEmpty());
}
static class ExistingConfig {
@Bean

View File

@@ -88,8 +88,11 @@ public class DispatcherServletAutoConfiguration {
private final WebMvcProperties webMvcProperties;
public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
private final ServerProperties serverProperties;
public DispatcherServletConfiguration(WebMvcProperties webMvcProperties, ServerProperties serverProperties) {
this.webMvcProperties = webMvcProperties;
this.serverProperties = serverProperties;
}
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
@@ -112,6 +115,11 @@ public class DispatcherServletAutoConfiguration {
return resolver;
}
@Bean
public DispatcherServletPathProvider mainDispatcherServletPathProvider() {
return () -> DispatcherServletConfiguration.this.serverProperties.getServlet().getPath();
}
}
@Configuration

View File

@@ -0,0 +1,34 @@
/*
* 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.autoconfigure.web.servlet;
import org.springframework.web.servlet.DispatcherServlet;
/**
* Interface that provides the path of the {@link DispatcherServlet} in
* an application context.
*
* @author Madhura Bhave
* @since 2.0.2
*/
@FunctionalInterface
public interface DispatcherServletPathProvider {
String getServletPath();
}

View File

@@ -107,6 +107,8 @@ public class DispatcherServletAutoConfigurationTests {
assertThat(registration.getUrlMappings().toString())
.isEqualTo("[/spring/*]");
assertThat(registration.getMultipartConfig()).isNull();
assertThat(context.getBean(DispatcherServletPathProvider.class)
.getServletPath()).isEqualTo("/spring");
});
}