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
This commit is contained in:
Andy Wilkinson
2026-09-08 08:51:58 +01:00
parent 159bdeb76e
commit 1d79f56ad7
2 changed files with 16 additions and 10 deletions
@@ -59,10 +59,13 @@ class SecurityInterceptor {
}
Mono<SecurityResponse> 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<SecurityResponse> 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) {
@@ -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