From b36edad27d3139cbb0971de1224454311cf20bd4 Mon Sep 17 00:00:00 2001 From: Lee JiWon Date: Sat, 4 Apr 2026 03:46:04 +0900 Subject: [PATCH] Preserve HttpMethod in EndpointRequest exclusions EndpointRequest.withHttpMethod(...) restrictions are lost when excluding(Class...), excluding(String...), or excludingLinks() are chained because each exclusion creates a new matcher without the configured method. Preserve the configured HttpMethod in both servlet and reactive matchers and add regression tests for exclusion chaining. See gh-49885 Signed-off-by: Lee JiWon --- .../security/reactive/EndpointRequest.java | 6 ++--- .../security/servlet/EndpointRequest.java | 6 ++--- .../reactive/EndpointRequestTests.java | 27 +++++++++++++++++++ .../servlet/EndpointRequestTests.java | 25 +++++++++++++++++ 4 files changed, 58 insertions(+), 6 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 bf4e08b0fc5..ceec49afdcd 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 @@ -301,17 +301,17 @@ public final class EndpointRequest { public EndpointServerWebExchangeMatcher excluding(Class... endpoints) { List excludes = new ArrayList<>(this.excludes); excludes.addAll(Arrays.asList((Object[]) endpoints)); - return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks, null); + return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks, this.httpMethod); } public EndpointServerWebExchangeMatcher excluding(String... endpoints) { List excludes = new ArrayList<>(this.excludes); excludes.addAll(Arrays.asList((Object[]) endpoints)); - return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks, null); + return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks, this.httpMethod); } public EndpointServerWebExchangeMatcher excludingLinks() { - return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, false, null); + return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, false, this.httpMethod); } /** 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 8c564b05697..e41be1933e1 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 @@ -335,17 +335,17 @@ public final class EndpointRequest { public EndpointRequestMatcher excluding(Class... endpoints) { List excludes = new ArrayList<>(this.excludes); excludes.addAll(Arrays.asList((Object[]) endpoints)); - return new EndpointRequestMatcher(this.includes, excludes, this.includeLinks, null); + return new EndpointRequestMatcher(this.includes, excludes, this.includeLinks, this.httpMethod); } public EndpointRequestMatcher excluding(String... endpoints) { List excludes = new ArrayList<>(this.excludes); excludes.addAll(Arrays.asList((Object[]) endpoints)); - return new EndpointRequestMatcher(this.includes, excludes, this.includeLinks, null); + return new EndpointRequestMatcher(this.includes, excludes, this.includeLinks, this.httpMethod); } public EndpointRequestMatcher excludingLinks() { - return new EndpointRequestMatcher(this.includes, this.excludes, false, null); + return new EndpointRequestMatcher(this.includes, this.excludes, false, this.httpMethod); } /** 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 641ae66ce67..3bc3fba059f 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 @@ -234,6 +234,33 @@ class EndpointRequestTests { assertMatcher.matches("/bar/"); } + @Test + void toAnyEndpointWithHttpMethodExcludingShouldPreserveHttpMethod() { + ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint() + .withHttpMethod(HttpMethod.POST) + .excluding(FooEndpoint.class) + .excluding("baz"); + List> endpoints = new ArrayList<>(); + endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo")); + endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar")); + endpoints.add(mockEndpoint(EndpointId.of("baz"), "baz")); + PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints("/actuator", () -> endpoints); + assertMatcher(matcher, pathMappedEndpoints).matches(HttpMethod.POST, "/actuator/bar"); + assertMatcher(matcher, pathMappedEndpoints).doesNotMatch(HttpMethod.GET, "/actuator/bar"); + assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/foo"); + assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/baz"); + } + + @Test + void toAnyEndpointWithHttpMethodExcludingLinksShouldPreserveHttpMethod() { + ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint() + .withHttpMethod(HttpMethod.POST) + .excludingLinks(); + assertMatcher(matcher).matches(HttpMethod.POST, "/actuator/foo"); + assertMatcher(matcher).doesNotMatch(HttpMethod.GET, "/actuator/foo"); + assertMatcher(matcher).doesNotMatch("/actuator"); + } + @Test void noEndpointPathsBeansShouldNeverMatch() { ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint(); 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 8e3fb115386..75e266d5ed0 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 @@ -222,6 +222,31 @@ class EndpointRequestTests { assertMatcher.matches("/bar"); } + @Test + void toAnyEndpointWithHttpMethodExcludingShouldPreserveHttpMethod() { + RequestMatcher matcher = EndpointRequest.toAnyEndpoint() + .withHttpMethod(HttpMethod.POST) + .excluding(FooEndpoint.class) + .excluding("baz"); + List> endpoints = new ArrayList<>(); + endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo")); + endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar")); + endpoints.add(mockEndpoint(EndpointId.of("baz"), "baz")); + PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints("/actuator", () -> endpoints); + assertMatcher(matcher, pathMappedEndpoints).matches(HttpMethod.POST, "/actuator/bar"); + assertMatcher(matcher, pathMappedEndpoints).doesNotMatch(HttpMethod.GET, "/actuator/bar"); + assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/foo"); + assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/baz"); + } + + @Test + void toAnyEndpointWithHttpMethodExcludingLinksShouldPreserveHttpMethod() { + RequestMatcher matcher = EndpointRequest.toAnyEndpoint().withHttpMethod(HttpMethod.POST).excludingLinks(); + assertMatcher(matcher).matches(HttpMethod.POST, "/actuator/foo"); + assertMatcher(matcher).doesNotMatch(HttpMethod.GET, "/actuator/foo"); + assertMatcher(matcher).doesNotMatch("/actuator"); + } + @Test void endpointRequestMatcherShouldUseCustomRequestMatcherProvider() { RequestMatcher matcher = EndpointRequest.toAnyEndpoint();