diff --git a/module/spring-boot-cloudfoundry/build.gradle b/module/spring-boot-cloudfoundry/build.gradle index 5482e1a7ca0..2be858c4e4b 100644 --- a/module/spring-boot-cloudfoundry/build.gradle +++ b/module/spring-boot-cloudfoundry/build.gradle @@ -29,7 +29,6 @@ dependencies { api(project(":module:spring-boot-actuator-autoconfigure")) optional(project(":module:spring-boot-health")) - optional(project(":module:spring-boot-restclient")) optional(project(":module:spring-boot-security")) optional(project(":module:spring-boot-webclient")) optional(project(":module:spring-boot-webflux")) diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java index aebc8276b5a..705d011ef80 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java @@ -51,7 +51,6 @@ import org.springframework.boot.cloudfoundry.autoconfigure.actuate.endpoint.Clou import org.springframework.boot.health.actuate.endpoint.HealthEndpoint; import org.springframework.boot.health.actuate.endpoint.HealthEndpointWebExtension; import org.springframework.boot.info.GitProperties; -import org.springframework.boot.restclient.RestTemplateBuilder; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -67,6 +66,7 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.web.client.RestClient; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.DispatcherServlet; @@ -115,15 +115,15 @@ public final class CloudFoundryActuatorAutoConfiguration { @SuppressWarnings("removal") CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping( ParameterValueMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, - RestTemplateBuilder restTemplateBuilder, + ObjectProvider restClientBuilder, org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier servletEndpointsSupplier, org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier controllerEndpointsSupplier, ApplicationContext applicationContext) { CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer(applicationContext, parameterMapper, endpointMediaTypes, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - SecurityInterceptor securityInterceptor = getSecurityInterceptor(restTemplateBuilder, - applicationContext.getEnvironment()); + SecurityInterceptor securityInterceptor = getSecurityInterceptor( + restClientBuilder.getIfAvailable(RestClient::builder), applicationContext.getEnvironment()); Collection webEndpoints = discoverer.getEndpoints(); List> allEndpoints = new ArrayList<>(); allEndpoints.addAll(webEndpoints); @@ -133,22 +133,21 @@ public final class CloudFoundryActuatorAutoConfiguration { endpointMediaTypes, getCorsConfiguration(), securityInterceptor, allEndpoints); } - private SecurityInterceptor getSecurityInterceptor(RestTemplateBuilder restTemplateBuilder, - Environment environment) { - SecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(restTemplateBuilder, environment); + private SecurityInterceptor getSecurityInterceptor(RestClient.Builder restClientBuilder, Environment environment) { + SecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(restClientBuilder, environment); TokenValidator tokenValidator = (cloudfoundrySecurityService != null) ? new TokenValidator(cloudfoundrySecurityService) : null; return new SecurityInterceptor(tokenValidator, cloudfoundrySecurityService, environment.getProperty("vcap.application.application_id")); } - private @Nullable SecurityService getCloudFoundrySecurityService(RestTemplateBuilder restTemplateBuilder, + private @Nullable SecurityService getCloudFoundrySecurityService(RestClient.Builder restClientBuilder, Environment environment) { String cloudControllerUrl = environment.getProperty("vcap.application.cf_api"); boolean skipSslValidation = environment.getProperty("management.cloudfoundry.skip-ssl-validation", Boolean.class, false); return (cloudControllerUrl != null) - ? new SecurityService(restTemplateBuilder, cloudControllerUrl, skipSslValidation) : null; + ? new SecurityService(restClientBuilder, cloudControllerUrl, skipSslValidation) : null; } private CorsConfiguration getCorsConfiguration() { diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityService.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityService.java index 132aa7f7768..9078796dbef 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityService.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityService.java @@ -27,14 +27,12 @@ import org.jspecify.annotations.Nullable; 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.boot.restclient.RestTemplateBuilder; import org.springframework.http.HttpStatus; -import org.springframework.http.RequestEntity; import org.springframework.util.Assert; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.RestClient; /** * Cloud Foundry security service to handle REST calls to the cloud controller and UAA. @@ -43,19 +41,19 @@ import org.springframework.web.client.RestTemplate; */ class SecurityService { - private final RestTemplate restTemplate; + private final RestClient restClient; private final String cloudControllerUrl; private @Nullable String uaaUrl; - SecurityService(RestTemplateBuilder restTemplateBuilder, String cloudControllerUrl, boolean skipSslValidation) { - Assert.notNull(restTemplateBuilder, "'restTemplateBuilder' must not be null"); + SecurityService(RestClient.Builder restClientBuilder, String cloudControllerUrl, boolean skipSslValidation) { + Assert.notNull(restClientBuilder, "'restClientBuilder' must not be null"); Assert.notNull(cloudControllerUrl, "'cloudControllerUrl' must not be null"); if (skipSslValidation) { - restTemplateBuilder = restTemplateBuilder.requestFactory(SkipSslVerificationHttpRequestFactory.class); + restClientBuilder = restClientBuilder.requestFactory(new SkipSslVerificationHttpRequestFactory()); } - this.restTemplate = restTemplateBuilder.build(); + this.restClient = restClientBuilder.build(); this.cloudControllerUrl = cloudControllerUrl; } @@ -69,8 +67,11 @@ class SecurityService { AccessLevel getAccessLevel(String token, String applicationId) throws CloudFoundryAuthorizationException { try { URI uri = getPermissionsUri(applicationId); - RequestEntity request = RequestEntity.get(uri).header("Authorization", "bearer " + token).build(); - Map body = this.restTemplate.exchange(request, Map.class).getBody(); + Map body = this.restClient.get() + .uri(uri) + .header("Authorization", "bearer " + token) + .retrieve() + .body(Map.class); if (body != null && Boolean.TRUE.equals(body.get("read_sensitive_data"))) { return AccessLevel.FULL; } @@ -102,7 +103,7 @@ class SecurityService { */ Map fetchTokenKeys() { try { - Map response = this.restTemplate.getForObject(getUaaUrl() + "/token_keys", Map.class); + Map response = this.restClient.get().uri(getUaaUrl() + "/token_keys").retrieve().body(Map.class); Assert.state(response != null, "'response' must not be null"); return extractTokenKeys(response); } @@ -129,7 +130,10 @@ class SecurityService { String getUaaUrl() { if (this.uaaUrl == null) { try { - Map response = this.restTemplate.getForObject(this.cloudControllerUrl + "/info", Map.class); + Map response = this.restClient.get() + .uri(this.cloudControllerUrl + "/info") + .retrieve() + .body(Map.class); Assert.state(response != null, "'response' must not be null"); String tokenEndpoint = (String) response.get("token_endpoint"); Assert.state(tokenEndpoint != null, "'tokenEndpoint' must not be null"); diff --git a/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfigurationTests.java b/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfigurationTests.java index 521fb969a68..b9f15a5a36e 100644 --- a/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfigurationTests.java +++ b/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfigurationTests.java @@ -42,7 +42,6 @@ import org.springframework.boot.health.autoconfigure.contributor.HealthContribut import org.springframework.boot.health.autoconfigure.registry.HealthContributorRegistryAutoConfiguration; import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration; -import org.springframework.boot.restclient.autoconfigure.RestTemplateAutoConfiguration; import org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration; import org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration; import org.springframework.boot.servlet.autoconfigure.actuate.web.ServletManagementContextAutoConfiguration; @@ -61,7 +60,6 @@ import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.assertj.MockMvcTester; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.client.RestTemplate; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.filter.CompositeFilter; @@ -87,7 +85,7 @@ class CloudFoundryActuatorAutoConfigurationTests { ServletWebSecurityAutoConfiguration.class, WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class, DispatcherServletAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, - RestTemplateAutoConfiguration.class, ManagementContextAutoConfiguration.class, + /* RestTemplateAutoConfiguration.class, */ ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, CloudFoundryActuatorAutoConfiguration.class)); @@ -164,15 +162,9 @@ class CloudFoundryActuatorAutoConfigurationTests { "management.cloudfoundry.skip-ssl-validation:true") .run((context) -> { CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping(context); - Object interceptor = ReflectionTestUtils.getField(handlerMapping, "securityInterceptor"); - assertThat(interceptor).isNotNull(); - Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, - "cloudFoundrySecurityService"); - assertThat(interceptorSecurityService).isNotNull(); - RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils.getField(interceptorSecurityService, - "restTemplate"); - assertThat(restTemplate).isNotNull(); - assertThat(restTemplate.getRequestFactory()).isInstanceOf(SkipSslVerificationHttpRequestFactory.class); + assertThat(handlerMapping) + .extracting("securityInterceptor.cloudFoundrySecurityService.restClient.clientRequestFactory") + .isInstanceOf(SkipSslVerificationHttpRequestFactory.class); }); } diff --git a/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityServiceTests.java b/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityServiceTests.java index 3eae46a36b2..b1c8c250bda 100644 --- a/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityServiceTests.java +++ b/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/SecurityServiceTests.java @@ -29,9 +29,8 @@ import org.springframework.boot.restclient.RestTemplateBuilder; import org.springframework.boot.restclient.test.MockServerRestTemplateCustomizer; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.client.MockRestServiceServer; -import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.RestClient; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -63,26 +62,24 @@ class SecurityServiceTests { void setup() { MockServerRestTemplateCustomizer mockServerCustomizer = new MockServerRestTemplateCustomizer(); RestTemplateBuilder builder = new RestTemplateBuilder(mockServerCustomizer); - this.securityService = new SecurityService(builder, CLOUD_CONTROLLER, false); + this.securityService = new SecurityService(RestClient.builder(builder.build()), CLOUD_CONTROLLER, false); this.server = mockServerCustomizer.getServer(); } @Test void skipSslValidationWhenTrue() { RestTemplateBuilder builder = new RestTemplateBuilder(); - this.securityService = new SecurityService(builder, CLOUD_CONTROLLER, true); - RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils.getField(this.securityService, "restTemplate"); - assertThat(restTemplate).isNotNull(); - assertThat(restTemplate.getRequestFactory()).isInstanceOf(SkipSslVerificationHttpRequestFactory.class); + this.securityService = new SecurityService(RestClient.builder(builder.build()), CLOUD_CONTROLLER, true); + assertThat(this.securityService).extracting("restClient.clientRequestFactory") + .isInstanceOf(SkipSslVerificationHttpRequestFactory.class); } @Test void doNotSkipSslValidationWhenFalse() { RestTemplateBuilder builder = new RestTemplateBuilder(); - this.securityService = new SecurityService(builder, CLOUD_CONTROLLER, false); - RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils.getField(this.securityService, "restTemplate"); - assertThat(restTemplate).isNotNull(); - assertThat(restTemplate.getRequestFactory()).isNotInstanceOf(SkipSslVerificationHttpRequestFactory.class); + this.securityService = new SecurityService(RestClient.builder(builder.build()), CLOUD_CONTROLLER, false); + assertThat(this.securityService).extracting("restClient.clientRequestFactory") + .isNotInstanceOf(SkipSslVerificationHttpRequestFactory.class); } @Test