diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java index e314602d79..ee8cf01fae 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java @@ -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 { + extends ApplicationContextRequestMatcher { private final List includes; @@ -140,7 +142,7 @@ public final class EndpointRequest { private EndpointRequestMatcher(List includes, List 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) { - this.delegate = createDelegate(pathMappedEndpoints); + protected void initialized(Supplier webApplicationContext) { + this.delegate = createDelegate(webApplicationContext); } private RequestMatcher createDelegate( - Supplier pathMappedEndpoints) { + Supplier 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 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 delegateMatchers = getDelegateMatchers(paths); + List 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 getDelegateMatchers(Set paths) { - return paths.stream().map((path) -> new AntPathRequestMatcher(path + "/**")) + private List getDelegateMatchers(String servletPath, Set paths) { + return paths.stream().map((path) -> new AntPathRequestMatcher(servletPath + path + "/**")) .collect(Collectors.toList()); } @Override protected boolean matches(HttpServletRequest request, - Supplier context) { + Supplier 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 { + extends ApplicationContextRequestMatcher { private RequestMatcher delegate; private LinksRequestMatcher() { - super(WebEndpointProperties.class); + super(WebApplicationContext.class); } @Override - protected void initialized(Supplier properties) { - this.delegate = createDelegate(properties.get()); + protected void initialized(Supplier 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 context) { + Supplier context) { return this.delegate.matches(request); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfiguration.java index 4a381d628a..92e9940ab1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfiguration.java @@ -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 () -> ""; + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java index bfd49618ff..87bf273683 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java @@ -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> 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) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationTests.java index 28d79548f3..3871fbe8e5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationTests.java @@ -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 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java index 01c8c14f1b..3350c990bb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java @@ -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 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathProvider.java new file mode 100644 index 0000000000..8cc387eafa --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletPathProvider.java @@ -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(); + +} + diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java index 68a62a5bbe..7927fb6e5a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfigurationTests.java @@ -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"); }); }