From 2d3273e0785b4127cb9c4dec6777009c603aa6e2 Mon Sep 17 00:00:00 2001 From: Lee JiWon Date: Fri, 17 Apr 2026 10:19:42 +0900 Subject: [PATCH] Always match the links endpoint with GET The links endpoint only supports GET, so its matcher is now hardcoded to GET. withHttpMethod(...) continues to apply only to endpoint paths and the behaviour is documented on its javadoc. Signed-off-by: Lee JiWon See gh-50095 --- .../security/reactive/EndpointRequest.java | 6 ++-- .../security/servlet/EndpointRequest.java | 6 ++-- .../reactive/EndpointRequestTests.java | 15 ++++++++-- .../servlet/EndpointRequestTests.java | 30 +++++++++++++------ 4 files changed, 42 insertions(+), 15 deletions(-) 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 ceec49afdcd5..63c9b6e7efe2 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 @@ -316,6 +316,8 @@ public EndpointServerWebExchangeMatcher excludingLinks() { /** * Restricts the matcher to only consider requests with a particular http method. + *

+ * The links endpoint, if included, is always matched using {@code GET}. * @param httpMethod the http method to include * @return a copy of the matcher further restricted to only match requests with * the specified http method @@ -373,9 +375,9 @@ protected ServerWebExchangeMatcher createDelegate(WebEndpointProperties properti String linksPath = getLinksPath(properties.getBasePath()); if (linksPath != null) { List linksMatchers = new ArrayList<>(); - linksMatchers.add(new PathPatternParserServerWebExchangeMatcher(linksPath)); + linksMatchers.add(new PathPatternParserServerWebExchangeMatcher(linksPath, HttpMethod.GET)); if (!linksPath.endsWith("/")) { - linksMatchers.add(new PathPatternParserServerWebExchangeMatcher(linksPath + "/")); + linksMatchers.add(new PathPatternParserServerWebExchangeMatcher(linksPath + "/", HttpMethod.GET)); } return new OrServerWebExchangeMatcher(linksMatchers); } 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 e41be1933e12..ab6117cbd6b2 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 @@ -227,9 +227,9 @@ protected final List getDelegateMatchers(RequestMatcherFactory r protected List getLinksMatchers(RequestMatcherFactory requestMatcherFactory, RequestMatcherProvider matcherProvider, String linksPath) { List linksMatchers = new ArrayList<>(); - linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, null, linksPath)); + linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, HttpMethod.GET, linksPath)); if (!linksPath.endsWith("/")) { - linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, null, linksPath, "/")); + linksMatchers.add(requestMatcherFactory.antPath(matcherProvider, HttpMethod.GET, linksPath, "/")); } return linksMatchers; } @@ -350,6 +350,8 @@ public EndpointRequestMatcher excludingLinks() { /** * Restricts the matcher to only consider requests with a particular HTTP method. + *

+ * The links endpoint, if included, is always matched using {@code GET}. * @param httpMethod the HTTP method to include * @return a copy of the matcher further restricted to only match requests with * the specified HTTP method 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 3bc3fba059fe..a7a7ab6fefba 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 @@ -75,6 +75,15 @@ void toAnyEndpointWithHttpMethodShouldRespectRequestMethod() { assertMatcher(matcher, "/actuator").doesNotMatch(HttpMethod.GET, "/actuator/foo"); } + @Test + void toAnyEndpointWithHttpMethodShouldUseGetForLinks() { + ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint().withHttpMethod(HttpMethod.POST); + assertMatcher(matcher, "/actuator").matches(HttpMethod.GET, "/actuator"); + assertMatcher(matcher, "/actuator").doesNotMatch(HttpMethod.POST, "/actuator"); + assertMatcher(matcher, "/actuator").matches(HttpMethod.GET, "/actuator/"); + assertMatcher(matcher, "/actuator").doesNotMatch(HttpMethod.POST, "/actuator/"); + } + @Test void toAnyEndpointShouldMatchEndpointPathWithTrailingSlash() { ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint(); @@ -140,8 +149,10 @@ void toLinksShouldOnlyMatchLinks() { ServerWebExchangeMatcher matcher = EndpointRequest.toLinks(); assertMatcher(matcher).doesNotMatch("/actuator/foo"); assertMatcher(matcher).doesNotMatch("/actuator/bar"); - assertMatcher(matcher).matches("/actuator"); - assertMatcher(matcher).matches("/actuator/"); + assertMatcher(matcher).matches(HttpMethod.GET, "/actuator"); + assertMatcher(matcher).doesNotMatch(HttpMethod.POST, "/actuator"); + assertMatcher(matcher).matches(HttpMethod.GET, "/actuator/"); + assertMatcher(matcher).doesNotMatch(HttpMethod.POST, "/actuator/"); } @Test 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 75e266d5ed08..96f69112d8f2 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 @@ -64,7 +64,7 @@ void toAnyEndpointShouldMatchEndpointPath() { assertMatcher(matcher, "/actuator").matches("/actuator/foo/zoo/"); assertMatcher(matcher, "/actuator").matches("/actuator/bar"); assertMatcher(matcher, "/actuator").matches("/actuator/bar/baz"); - assertMatcher(matcher, "/actuator").matches("/actuator"); + assertMatcher(matcher, "/actuator").matches(HttpMethod.GET, "/actuator"); } @Test @@ -75,12 +75,22 @@ void toAnyEndpointWithHttpMethodShouldRespectRequestMethod() { assertMatcher(matcher, "/actuator").doesNotMatch(HttpMethod.GET, "/actuator/foo"); } + @Test + void toAnyEndpointWithHttpMethodShouldUseGetForLinks() { + EndpointRequest.EndpointRequestMatcher matcher = EndpointRequest.toAnyEndpoint() + .withHttpMethod(HttpMethod.POST); + assertMatcher(matcher, "/actuator").matches(HttpMethod.GET, "/actuator"); + assertMatcher(matcher, "/actuator").doesNotMatch(HttpMethod.POST, "/actuator"); + assertMatcher(matcher, "/actuator").matches(HttpMethod.GET, "/actuator/"); + assertMatcher(matcher, "/actuator").doesNotMatch(HttpMethod.POST, "/actuator/"); + } + @Test void toAnyEndpointShouldMatchEndpointPathWithTrailingSlash() { RequestMatcher matcher = EndpointRequest.toAnyEndpoint(); assertMatcher(matcher, "/actuator").matches("/actuator/foo/"); assertMatcher(matcher, "/actuator").matches("/actuator/bar/"); - assertMatcher(matcher, "/actuator").matches("/actuator/"); + assertMatcher(matcher, "/actuator").matches(HttpMethod.GET, "/actuator/"); } @Test @@ -97,7 +107,7 @@ void toAnyEndpointWhenBasePathIsEmptyAndManagementPortDifferentShouldMatchLinks( RequestMatcher matcher = EndpointRequest.toAnyEndpoint(); RequestMatcherAssert assertMatcher = assertMatcher(matcher, mockPathMappedEndpoints(""), null, WebServerNamespace.MANAGEMENT); - assertMatcher.matches("/"); + assertMatcher.matches(HttpMethod.GET, "/"); assertMatcher.matches("/foo"); } @@ -112,7 +122,7 @@ void toAnyEndpointWhenDispatcherServletPathProviderNotAvailableUsesEmptyPath() { RequestMatcher matcher = EndpointRequest.toAnyEndpoint(); assertMatcher(matcher, "/actuator").matches("/actuator/foo"); assertMatcher(matcher, "/actuator").matches("/actuator/bar"); - assertMatcher(matcher, "/actuator").matches("/actuator"); + assertMatcher(matcher, "/actuator").matches(HttpMethod.GET, "/actuator"); assertMatcher(matcher, "/actuator").doesNotMatch("/actuator/baz"); } @@ -147,8 +157,10 @@ void toLinksShouldOnlyMatchLinks() { RequestMatcher matcher = EndpointRequest.toLinks(); assertMatcher(matcher).doesNotMatch("/actuator/foo"); assertMatcher(matcher).doesNotMatch("/actuator/bar"); - assertMatcher(matcher).matches("/actuator"); - assertMatcher(matcher).matches("/actuator/"); + assertMatcher(matcher).matches(HttpMethod.GET, "/actuator"); + assertMatcher(matcher).doesNotMatch(HttpMethod.POST, "/actuator"); + assertMatcher(matcher).matches(HttpMethod.GET, "/actuator/"); + assertMatcher(matcher).doesNotMatch(HttpMethod.POST, "/actuator/"); } @Test @@ -165,7 +177,7 @@ void toLinksWhenBasePathEmptyAndManagementPortDifferentShouldMatchRoot() { RequestMatcher matcher = EndpointRequest.toLinks(); RequestMatcherAssert assertMatcher = assertMatcher(matcher, mockPathMappedEndpoints(""), null, WebServerNamespace.MANAGEMENT); - assertMatcher.matches("/"); + assertMatcher.matches(HttpMethod.GET, "/"); assertMatcher.doesNotMatch("/foo"); } @@ -180,7 +192,7 @@ void excludeByClassShouldNotMatchExcluded() { assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/foo"); assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/baz"); assertMatcher(matcher).matches("/actuator/bar"); - assertMatcher(matcher).matches("/actuator"); + assertMatcher(matcher).matches(HttpMethod.GET, "/actuator"); } @Test @@ -195,7 +207,7 @@ void excludeByIdShouldNotMatchExcluded() { RequestMatcher matcher = EndpointRequest.toAnyEndpoint().excluding("foo"); assertMatcher(matcher).doesNotMatch("/actuator/foo"); assertMatcher(matcher).matches("/actuator/bar"); - assertMatcher(matcher).matches("/actuator"); + assertMatcher(matcher).matches(HttpMethod.GET, "/actuator"); } @Test