From 1f2ea4a6db03eb1d666a174a525f2bff658b8296 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Wed, 18 Mar 2026 17:35:22 +0100 Subject: [PATCH] Revisit EndpointRequest matcher for additional paths Prior to this commit, the `EndpointRequest` request matcher generator would create many, complex matchers for the case of health groups exposed on additional paths. This commit revisits the matcher generation and simplifies their matching to avoid conflicts with other matchers. Fixes gh-49648 --- .../autoconfigure/security/reactive/EndpointRequest.java | 6 +++--- .../autoconfigure/security/servlet/EndpointRequest.java | 7 +++---- .../security/reactive/EndpointRequestTests.java | 1 + .../security/servlet/EndpointRequestTests.java | 1 + 4 files changed, 8 insertions(+), 7 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 5e09360669b..f2bbd90ca23 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 @@ -441,12 +441,12 @@ public final class EndpointRequest { @Override protected ServerWebExchangeMatcher createDelegate(PathMappedEndpoints endpoints) { - Set paths = this.endpoints.stream() + List delegateMatchers = this.endpoints.stream() .filter(Objects::nonNull) .map(this::getEndpointId) .flatMap((endpointId) -> streamAdditionalPaths(endpoints, endpointId)) - .collect(Collectors.toCollection(LinkedHashSet::new)); - List delegateMatchers = getDelegateMatchers(paths, this.httpMethod); + .map(PathPatternParserServerWebExchangeMatcher::new) + .collect(Collectors.toCollection(ArrayList::new)); return (!CollectionUtils.isEmpty(delegateMatchers)) ? new OrServerWebExchangeMatcher(delegateMatchers) : 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 f0e5f28734e..8c564b05697 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 @@ -475,13 +475,12 @@ public final class EndpointRequest { RequestMatcherFactory requestMatcherFactory) { PathMappedEndpoints endpoints = context.getBean(PathMappedEndpoints.class); RequestMatcherProvider matcherProvider = getRequestMatcherProvider(context); - Set paths = this.endpoints.stream() + List delegateMatchers = this.endpoints.stream() .filter(Objects::nonNull) .map(this::getEndpointId) .flatMap((endpointId) -> streamAdditionalPaths(endpoints, endpointId)) - .collect(Collectors.toCollection(LinkedHashSet::new)); - List delegateMatchers = getDelegateMatchers(requestMatcherFactory, matcherProvider, paths, - this.httpMethod); + .map((path) -> requestMatcherFactory.antPath(matcherProvider, this.httpMethod, path)) + .collect(Collectors.toCollection(ArrayList::new)); return (!CollectionUtils.isEmpty(delegateMatchers)) ? new OrRequestMatcher(delegateMatchers) : 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 151ea82b28c..a7321f58adb 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 @@ -315,6 +315,7 @@ class EndpointRequestTests { FooEndpoint.class); RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", () -> List.of(mockEndpoint(EndpointId.of("foo"), "test", WebServerNamespace.SERVER, "/additional")))); + assertMatcher.doesNotMatch("/additional/foo"); assertMatcher.doesNotMatch("/foo"); assertMatcher.doesNotMatch("/bar"); } 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 7a7620f2564..8e3fb115386 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 @@ -321,6 +321,7 @@ class EndpointRequestTests { FooEndpoint.class); RequestMatcherAssert assertMatcher = assertMatcher(matcher, new PathMappedEndpoints("", () -> List.of(mockEndpoint(EndpointId.of("foo"), "test", WebServerNamespace.SERVER, "/additional")))); + assertMatcher.doesNotMatch("/additional/foo"); assertMatcher.doesNotMatch("/foo"); assertMatcher.doesNotMatch("/bar"); }