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 ceec49afdcd..63c9b6e7efe 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 final class EndpointRequest { /** * 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 @@ public final class EndpointRequest { 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 e41be1933e1..ab6117cbd6b 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 @@ public final class EndpointRequest { 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 final class EndpointRequest { /** * 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 3bc3fba059f..a7a7ab6fefb 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 @@ class EndpointRequestTests { 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 @@ class EndpointRequestTests { 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 75e266d5ed0..96f69112d8f 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 @@ class EndpointRequestTests { 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 @@ class EndpointRequestTests { 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 @@ class EndpointRequestTests { 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 @@ class EndpointRequestTests { 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 @@ class EndpointRequestTests { 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 @@ class EndpointRequestTests { 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 @@ class EndpointRequestTests { 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 @@ class EndpointRequestTests { 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