mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-10-08 10:19:13 +00:00
Merge branch '4.0.x'
Closes gh-48827
This commit is contained in:
@@ -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"))
|
||||
|
||||
+8
-9
@@ -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<RestClient.Builder> 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<ExposableWebEndpoint> webEndpoints = discoverer.getEndpoints();
|
||||
List<ExposableEndpoint<?>> 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() {
|
||||
|
||||
+16
-12
@@ -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<String, String> 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");
|
||||
|
||||
+4
-12
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+8
-11
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user