Browse Source

Fix EndpointRequest links matching for separate management port

When management.endpoints.web.base-path is empty and management runs
on a different port, EndpointRequest.toLinks() and toAnyEndpoint()
do not match the links endpoint consistently.

Derive the links path based on the management port type on both
servlet and reactive sides, and add regression tests for each
implementation.

See gh-49591

Signed-off-by: bbbbooo <hyeons1213@gmail.com>
pull/49591/head
bbbbooo 1 month ago committed by Andy Wilkinson
parent
commit
79f91eac56
  1. 25
      spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java
  2. 31
      spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java
  3. 39
      spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java
  4. 39
      spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java

25
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 { @@ -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<Object> endpoints, String emptyValue) {
return (!endpoints.isEmpty()) ? endpoints.stream()
.map(this::getEndpointId)
@ -326,7 +333,8 @@ public final class EndpointRequest { @@ -326,7 +333,8 @@ public final class EndpointRequest {
streamPaths(this.includes, endpoints).forEach(paths::add);
streamPaths(this.excludes, endpoints).forEach(paths::remove);
List<ServerWebExchangeMatcher> 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 { @@ -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<ServerWebExchangeMatcher> 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;
}

31
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 { @@ -225,13 +225,27 @@ public final class EndpointRequest {
}
protected List<RequestMatcher> getLinksMatchers(RequestMatcherFactory requestMatcherFactory,
RequestMatcherProvider matcherProvider, String basePath) {
RequestMatcherProvider matcherProvider, String linksPath) {
List<RequestMatcher> 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 { @@ -359,8 +373,9 @@ public final class EndpointRequest {
List<RequestMatcher> 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 { @@ -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;
}

39
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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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) {

39
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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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) {

Loading…
Cancel
Save