diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java index 6702142749b..dcaad8d5564 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java @@ -228,6 +228,13 @@ public final class EndpointRequest { && applicationContext.getParent() == null; } + protected final String getLinksPath(String basePath) { + if (StringUtils.hasText(basePath)) { + return basePath; + } + return (this.managementPortType == ManagementPortType.DIFFERENT) ? "/" : null; + } + protected final String toString(List endpoints, String emptyValue) { return (!endpoints.isEmpty()) ? endpoints.stream() .map(this::getEndpointId) @@ -326,7 +333,8 @@ public final class EndpointRequest { streamPaths(this.includes, endpoints).forEach(paths::add); streamPaths(this.excludes, endpoints).forEach(paths::remove); List delegateMatchers = getDelegateMatchers(paths, this.httpMethod); - if (this.includeLinks && StringUtils.hasText(endpoints.getBasePath())) { + String linksPath = getLinksPath(endpoints.getBasePath()); + if (this.includeLinks && linksPath != null) { delegateMatchers.add(new LinksServerWebExchangeMatcher()); } if (delegateMatchers.isEmpty()) { @@ -362,10 +370,17 @@ public final class EndpointRequest { @Override protected ServerWebExchangeMatcher createDelegate(WebEndpointProperties properties) { - if (StringUtils.hasText(properties.getBasePath())) { - return new OrServerWebExchangeMatcher( - new PathPatternParserServerWebExchangeMatcher(properties.getBasePath()), - new PathPatternParserServerWebExchangeMatcher(properties.getBasePath() + "/")); + String linksPath = getLinksPath(properties.getBasePath()); + if (linksPath != null) { + List linksMatchers = new ArrayList<>(); + linksMatchers.add(new PathPatternParserServerWebExchangeMatcher(linksPath)); + if ("/".equals(linksPath)) { + linksMatchers.add(new PathPatternParserServerWebExchangeMatcher("")); + } + else { + linksMatchers.add(new PathPatternParserServerWebExchangeMatcher(linksPath + "/")); + } + return new OrServerWebExchangeMatcher(linksMatchers); } return EMPTY_MATCHER; } 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 772ed58be3b..e8a16bd185a 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 @@ -225,13 +225,27 @@ public final class EndpointRequest { } protected List getLinksMatchers(RequestMatcherFactory requestMatcherFactory, - RequestMatcherProvider matcherProvider, String basePath) { + RequestMatcherProvider matcherProvider, String linksPath) { List linksMatchers = new ArrayList<>(); - linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, null, basePath)); - linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, null, basePath, "/")); + linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, null, linksPath)); + if (!"/".equals(linksPath)) { + linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, null, linksPath, "/")); + } return linksMatchers; } + protected String getLinksPath(WebApplicationContext context, String basePath) { + if (StringUtils.hasText(basePath)) { + return basePath; + } + ManagementPortType managementPortType = this.managementPortType; + if (managementPortType == null) { + managementPortType = ManagementPortType.get(context.getEnvironment()); + this.managementPortType = managementPortType; + } + return (managementPortType == ManagementPortType.DIFFERENT) ? "/" : null; + } + protected RequestMatcherProvider getRequestMatcherProvider(WebApplicationContext context) { try { return getRequestMatcherProviderBean(context); @@ -359,8 +373,9 @@ public final class EndpointRequest { List delegateMatchers = getDelegateMatchers(requestMatcherFactory, matcherProvider, paths, this.httpMethod); String basePath = endpoints.getBasePath(); - if (this.includeLinks && StringUtils.hasText(basePath)) { - delegateMatchers.addAll(getLinksMatchers(requestMatcherFactory, matcherProvider, basePath)); + String linksPath = getLinksPath(context, basePath); + if (this.includeLinks && linksPath != null) { + delegateMatchers.addAll(getLinksMatchers(requestMatcherFactory, matcherProvider, linksPath)); } if (delegateMatchers.isEmpty()) { return EMPTY_MATCHER; @@ -393,10 +408,10 @@ public final class EndpointRequest { protected RequestMatcher createDelegate(WebApplicationContext context, RequestMatcherFactory requestMatcherFactory) { WebEndpointProperties properties = context.getBean(WebEndpointProperties.class); - String basePath = properties.getBasePath(); - if (StringUtils.hasText(basePath)) { + String linksPath = getLinksPath(context, properties.getBasePath()); + if (linksPath != null) { return new OrRequestMatcher( - getLinksMatchers(requestMatcherFactory, getRequestMatcherProvider(context), basePath)); + getLinksMatchers(requestMatcherFactory, getRequestMatcherProvider(context), linksPath)); } return EMPTY_MATCHER; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java index 721e576da96..c1fbc54c65e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java @@ -20,6 +20,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import org.assertj.core.api.AssertDelegateTarget; import org.junit.jupiter.api.Test; @@ -35,6 +36,7 @@ import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.web.context.WebServerApplicationContext; import org.springframework.boot.web.server.WebServer; import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.env.MapPropertySource; import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -91,6 +93,15 @@ class EndpointRequestTests { assertMatcher.matches("/bar"); } + @Test + void toAnyEndpointWhenBasePathIsEmptyAndManagementPortDifferentShouldMatchLinks() { + ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint(); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, mockPathMappedEndpoints(""), + WebServerNamespace.MANAGEMENT, true); + assertMatcher.matches("/"); + assertMatcher.matches("/foo"); + } + @Test void toAnyEndpointShouldNotMatchOtherPath() { ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint(); @@ -143,6 +154,15 @@ class EndpointRequestTests { assertMatcher.doesNotMatch("/"); } + @Test + void toLinksWhenBasePathEmptyAndManagementPortDifferentShouldMatchRoot() { + ServerWebExchangeMatcher matcher = EndpointRequest.toLinks(); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, mockPathMappedEndpoints(""), + WebServerNamespace.MANAGEMENT, true); + assertMatcher.matches("/"); + assertMatcher.doesNotMatch("/foo"); + } + @Test void excludeByClassShouldNotMatchExcluded() { ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint() @@ -325,10 +345,25 @@ class EndpointRequestTests { private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher, PathMappedEndpoints pathMappedEndpoints, WebServerNamespace namespace) { + return assertMatcher(matcher, pathMappedEndpoints, namespace, false); + } + + private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher, + PathMappedEndpoints pathMappedEndpoints, WebServerNamespace namespace, boolean managementPortDifferent) { StaticApplicationContext context = new StaticApplicationContext(); if (namespace != null && !WebServerNamespace.SERVER.equals(namespace)) { - NamedStaticWebApplicationContext parentContext = new NamedStaticWebApplicationContext(namespace); - context.setParent(parentContext); + if (managementPortDifferent) { + context = new NamedStaticWebApplicationContext(namespace); + } + else { + NamedStaticWebApplicationContext parentContext = new NamedStaticWebApplicationContext(namespace); + context.setParent(parentContext); + } + } + if (managementPortDifferent) { + context.getEnvironment() + .getPropertySources() + .addFirst(new MapPropertySource("test", Map.of("management.server.port", 0))); } context.registerBean(WebEndpointProperties.class); if (pathMappedEndpoints != null) { 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 0a16d3cade7..6e1c21029e9 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 @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.security.servlet; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import org.assertj.core.api.AssertDelegateTarget; @@ -36,6 +37,7 @@ import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.web.context.WebServerApplicationContext; import org.springframework.boot.web.server.WebServer; +import org.springframework.core.env.MapPropertySource; import org.springframework.http.HttpMethod; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockServletContext; @@ -91,6 +93,15 @@ class EndpointRequestTests { assertMatcher.matches("/bar"); } + @Test + void toAnyEndpointWhenBasePathIsEmptyAndManagementPortDifferentShouldMatchLinks() { + RequestMatcher matcher = EndpointRequest.toAnyEndpoint(); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, mockPathMappedEndpoints(""), null, + WebServerNamespace.MANAGEMENT, true); + assertMatcher.matches("/"); + assertMatcher.matches("/foo"); + } + @Test void toAnyEndpointShouldNotMatchOtherPath() { RequestMatcher matcher = EndpointRequest.toAnyEndpoint(); @@ -150,6 +161,15 @@ class EndpointRequestTests { assertMatcher.doesNotMatch("/"); } + @Test + void toLinksWhenBasePathEmptyAndManagementPortDifferentShouldMatchRoot() { + RequestMatcher matcher = EndpointRequest.toLinks(); + RequestMatcherAssert assertMatcher = assertMatcher(matcher, mockPathMappedEndpoints(""), null, + WebServerNamespace.MANAGEMENT, true); + assertMatcher.matches("/"); + assertMatcher.doesNotMatch("/foo"); + } + @Test void excludeByClassShouldNotMatchExcluded() { RequestMatcher matcher = EndpointRequest.toAnyEndpoint().excluding(FooEndpoint.class, BazServletEndpoint.class); @@ -350,10 +370,25 @@ class EndpointRequestTests { private RequestMatcherAssert assertMatcher(RequestMatcher matcher, PathMappedEndpoints pathMappedEndpoints, RequestMatcherProvider matcherProvider, WebServerNamespace namespace) { + return assertMatcher(matcher, pathMappedEndpoints, matcherProvider, namespace, false); + } + + private RequestMatcherAssert assertMatcher(RequestMatcher matcher, PathMappedEndpoints pathMappedEndpoints, + RequestMatcherProvider matcherProvider, WebServerNamespace namespace, boolean managementPortDifferent) { StaticWebApplicationContext context = new StaticWebApplicationContext(); if (namespace != null && !WebServerNamespace.SERVER.equals(namespace)) { - NamedStaticWebApplicationContext parentContext = new NamedStaticWebApplicationContext(namespace); - context.setParent(parentContext); + if (managementPortDifferent) { + context = new NamedStaticWebApplicationContext(namespace); + } + else { + NamedStaticWebApplicationContext parentContext = new NamedStaticWebApplicationContext(namespace); + context.setParent(parentContext); + } + } + if (managementPortDifferent) { + context.getEnvironment() + .getPropertySources() + .addFirst(new MapPropertySource("test", Map.of("management.server.port", 0))); } context.registerBean(WebEndpointProperties.class); if (pathMappedEndpoints != null) {