From 847b2e837b3dbdea26a0fac1ef9f6ae2c17e3832 Mon Sep 17 00:00:00 2001 From: Lee JiWon Date: Fri, 10 Apr 2026 13:58:17 +0900 Subject: [PATCH] Strip query string from Cloud Foundry reactive links base URL CloudFoundryLinksHandler passes request.getURI().toString() directly to EndpointLinksResolver.resolveLinks(), which includes the query string. This causes generated link hrefs to contain the query (for example, "/cfApplication?x=1/info"). The standard WebFlux and Cloud Foundry servlet siblings already strip the query before resolving links. Use UriComponentsBuilder.replaceQuery(null) to match the pattern used by WebFluxEndpointHandlerMapping and add a regression test for a query-string request. Signed-off-by: Lee JiWon See gh-50008 --- ...dFoundryWebFluxEndpointHandlerMapping.java | 4 +++- ...oundryWebFluxEndpointIntegrationTests.java | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java index 50833eb8fce..8f8648aabdf 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java @@ -49,6 +49,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.UriComponentsBuilder; /** * A custom {@link RequestMappingInfoHandlerMapping} that makes web endpoints available on @@ -110,8 +111,9 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH return new ResponseEntity<>(securityResponse.getStatus()); } AccessLevel accessLevel = exchange.getAttribute(AccessLevel.REQUEST_ATTRIBUTE); + String requestUri = UriComponentsBuilder.fromUri(request.getURI()).replaceQuery(null).toUriString(); Map links = CloudFoundryWebFluxEndpointHandlerMapping.this.linksResolver - .resolveLinks(request.getURI().toString()); + .resolveLinks(requestUri); return new ResponseEntity<>( Collections.singletonMap("_links", getAccessibleLinks(accessLevel, links)), HttpStatus.OK); }); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java index ed973e4c3aa..df1aa433bea 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java @@ -61,6 +61,7 @@ import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.cors.CorsConfiguration; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -166,6 +167,26 @@ class CloudFoundryWebFluxEndpointIntegrationTests { .isEqualTo(true))); } + @Test + void linksToOtherEndpointsWithQueryStringShouldNotContainQueryInHref() { + given(this.tokenValidator.validate(any())).willReturn(Mono.empty()); + given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.FULL)); + this.contextRunner.run(withWebTestClient((client) -> client.get() + .uri("/cfApplication?x=1") + .accept(MediaType.APPLICATION_JSON) + .header("Authorization", "bearer " + mockAccessToken()) + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("_links.self.href") + .value((href) -> assertThat((String) href).doesNotContain("?").endsWith("/cfApplication")) + .jsonPath("_links.info.href") + .value((href) -> assertThat((String) href).doesNotContain("?").endsWith("/cfApplication/info")) + .jsonPath("_links.env.href") + .value((href) -> assertThat((String) href).doesNotContain("?").endsWith("/cfApplication/env")))); + } + @Test void linksToOtherEndpointsForbidden() { CloudFoundryAuthorizationException exception = new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,