From 1d79f56ad7ea82bd1ed3402a684d4c4a8659d4b1 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 8 Sep 2026 08:51:58 +0100 Subject: [PATCH] Return 503 for missing application ID or cloud controller URL Previously, the CloudFoundryAuthorizationExceptions thrown for a missing application ID or cloud controller URL were not routed through the error handling that turned them into a SecurityResponse with a 503 status code. As a result, they were processed by the general exception handling which resulted in a 500 response. This commit updates the reactive SecurityInterceptor so that the exceptions are processed through SecurityInterceptor::getErrorResponse. This aligns the behavior of the Cloud Foundry security intergration in a reactive application more closely with that of a Servlet application. Fixes gh-51613 --- .../endpoint/reactive/SecurityInterceptor.java | 9 ++++++--- .../reactive/SecurityInterceptorTests.java | 17 ++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/SecurityInterceptor.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/SecurityInterceptor.java index c78f15db091..69d782b7d33 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/SecurityInterceptor.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/SecurityInterceptor.java @@ -59,10 +59,13 @@ class SecurityInterceptor { } Mono preHandle(ServerWebExchange exchange, String id) { - ServerHttpRequest request = exchange.getRequest(); - if (CorsUtils.isPreFlightRequest(request)) { + if (CorsUtils.isPreFlightRequest(exchange.getRequest())) { return SUCCESS; } + return doPreHandle(exchange, id).doOnError(this::logError).onErrorResume(this::getErrorResponse); + } + + private Mono doPreHandle(ServerWebExchange exchange, String id) { if (!StringUtils.hasText(this.applicationId)) { return Mono.error(new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, "Application id is not available")); @@ -71,7 +74,7 @@ class SecurityInterceptor { return Mono.error(new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, "Cloud controller URL is not available")); } - return check(exchange, id).then(SUCCESS).doOnError(this::logError).onErrorResume(this::getErrorResponse); + return check(exchange, id).then(SUCCESS); } private void logError(Throwable ex) { diff --git a/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/SecurityInterceptorTests.java b/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/SecurityInterceptorTests.java index ac9c05c760f..df1004b35b0 100644 --- a/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/SecurityInterceptorTests.java +++ b/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/SecurityInterceptorTests.java @@ -28,7 +28,6 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import org.springframework.boot.cloudfoundry.autoconfigure.actuate.endpoint.AccessLevel; -import org.springframework.boot.cloudfoundry.autoconfigure.actuate.endpoint.CloudFoundryAuthorizationException; import org.springframework.boot.cloudfoundry.autoconfigure.actuate.endpoint.CloudFoundryAuthorizationException.Reason; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -100,9 +99,11 @@ class SecurityInterceptorTests { .header(HttpHeaders.AUTHORIZATION, "bearer " + mockAccessToken()) .build()); StepVerifier.create(this.interceptor.preHandle(request, "/a")) - .consumeErrorWith((ex) -> assertThat(((CloudFoundryAuthorizationException) ex).getReason()) - .isEqualTo(Reason.SERVICE_UNAVAILABLE)) - .verify(); + .consumeNextWith( + (response) -> assertThat(response.getStatus()).isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus()) + .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus())) + .expectComplete() + .verify(Duration.ofSeconds(30)); } @Test @@ -111,9 +112,11 @@ class SecurityInterceptorTests { MockServerWebExchange request = MockServerWebExchange .from(MockServerHttpRequest.get("/a").header(HttpHeaders.AUTHORIZATION, mockAccessToken()).build()); StepVerifier.create(this.interceptor.preHandle(request, "/a")) - .consumeErrorWith((ex) -> assertThat(((CloudFoundryAuthorizationException) ex).getReason()) - .isEqualTo(Reason.SERVICE_UNAVAILABLE)) - .verify(); + .consumeNextWith( + (response) -> assertThat(response.getStatus()).isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus()) + .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus())) + .expectComplete() + .verify(Duration.ofSeconds(30)); } @Test