Browse Source

Merge branch '3.5.x' into 4.0.x

Closes gh-49646
4.0.x
Brian Clozel 6 days ago
parent
commit
1ead08b9d8
  1. 25
      module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfiguration.java
  2. 6
      module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java
  3. 16
      module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java
  4. 6
      module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryWebEndpointServletHandlerMapping.java
  5. 2
      module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfigurationTests.java
  6. 6
      module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java
  7. 4
      module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfigurationTests.java
  8. 11
      module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java
  9. 44
      module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/actuate/endpoint/web/AbstractWebFluxEndpointHandlerMapping.java
  10. 36
      module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AbstractWebMvcEndpointHandlerMapping.java

25
module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfiguration.java

@ -21,7 +21,6 @@ import java.util.Arrays; @@ -21,7 +21,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
@ -35,7 +34,6 @@ import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; @@ -35,7 +34,6 @@ import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.actuate.info.GitInfoContributor;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.actuate.info.InfoEndpoint;
@ -65,7 +63,6 @@ import org.springframework.security.web.server.MatcherSecurityWebFilterChain; @@ -65,7 +63,6 @@ import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.util.function.SingletonSupplier;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.WebFilter;
@ -159,20 +156,15 @@ public final class CloudFoundryReactiveActuatorAutoConfiguration { @@ -159,20 +156,15 @@ public final class CloudFoundryReactiveActuatorAutoConfiguration {
static class IgnoredPathsSecurityConfiguration {
@Bean
static WebFilterChainPostProcessor webFilterChainPostProcessor(
ObjectProvider<CloudFoundryWebFluxEndpointHandlerMapping> handlerMapping) {
return new WebFilterChainPostProcessor(handlerMapping);
static WebFilterChainPostProcessor webFilterChainPostProcessor() {
return new WebFilterChainPostProcessor();
}
}
static class WebFilterChainPostProcessor implements BeanPostProcessor {
private final Supplier<PathMappedEndpoints> pathMappedEndpoints;
WebFilterChainPostProcessor(ObjectProvider<CloudFoundryWebFluxEndpointHandlerMapping> handlerMapping) {
this.pathMappedEndpoints = SingletonSupplier
.of(() -> new PathMappedEndpoints(BASE_PATH, () -> handlerMapping.getObject().getAllEndpoints()));
WebFilterChainPostProcessor() {
}
@Override
@ -184,9 +176,8 @@ public final class CloudFoundryReactiveActuatorAutoConfiguration { @@ -184,9 +176,8 @@ public final class CloudFoundryReactiveActuatorAutoConfiguration {
}
private WebFilterChainProxy postProcess(WebFilterChainProxy existing) {
List<String> paths = getPaths(this.pathMappedEndpoints.get());
ServerWebExchangeMatcher cloudFoundryRequestMatcher = ServerWebExchangeMatchers
.pathMatchers(paths.toArray(new String[] {}));
.pathMatchers(BASE_PATH + "/**");
WebFilter noOpFilter = (exchange, chain) -> chain.filter(exchange);
MatcherSecurityWebFilterChain ignoredRequestFilterChain = new MatcherSecurityWebFilterChain(
cloudFoundryRequestMatcher, Collections.singletonList(noOpFilter));
@ -195,14 +186,6 @@ public final class CloudFoundryReactiveActuatorAutoConfiguration { @@ -195,14 +186,6 @@ public final class CloudFoundryReactiveActuatorAutoConfiguration {
return new WebFilterChainProxy(ignoredRequestFilterChain, allRequestsFilterChain);
}
private static List<String> getPaths(PathMappedEndpoints pathMappedEndpoints) {
List<String> paths = new ArrayList<>();
pathMappedEndpoints.getAllPaths().forEach((path) -> paths.add(path + "/**"));
paths.add(BASE_PATH);
paths.add(BASE_PATH + "/");
return paths;
}
}
}

6
module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java

@ -78,6 +78,12 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH @@ -78,6 +78,12 @@ class CloudFoundryWebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointH
this.securityInterceptor = securityInterceptor;
}
@Override
protected void initHandlerMethods() {
super.initHandlerMethods();
registerCatchAllMapping(HttpStatus.FORBIDDEN);
}
@Override
protected ReactiveWebOperation wrapReactiveWebOperation(ExposableWebEndpoint endpoint, WebOperation operation,
ReactiveWebOperation reactiveWebOperation) {

16
module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfiguration.java

@ -32,7 +32,6 @@ import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; @@ -32,7 +32,6 @@ import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.actuate.info.GitInfoContributor;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.actuate.info.InfoEndpoint;
@ -64,7 +63,6 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity; @@ -64,7 +63,6 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
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;
@ -172,22 +170,16 @@ public final class CloudFoundryActuatorAutoConfiguration { @@ -172,22 +170,16 @@ public final class CloudFoundryActuatorAutoConfiguration {
@Bean
@Order(FILTER_CHAIN_ORDER)
SecurityFilterChain cloudFoundrySecurityFilterChain(HttpSecurity http,
CloudFoundryWebEndpointServletHandlerMapping handlerMapping) {
RequestMatcher cloudFoundryRequest = getRequestMatcher(handlerMapping);
SecurityFilterChain cloudFoundrySecurityFilterChain(HttpSecurity http) throws Exception {
RequestMatcher cloudFoundryRequest = getRequestMatcher();
http.csrf((csrf) -> csrf.ignoringRequestMatchers(cloudFoundryRequest));
http.securityMatchers((matches) -> matches.requestMatchers(cloudFoundryRequest))
.authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll());
return http.build();
}
private RequestMatcher getRequestMatcher(CloudFoundryWebEndpointServletHandlerMapping handlerMapping) {
PathMappedEndpoints endpoints = new PathMappedEndpoints(BASE_PATH, handlerMapping::getAllEndpoints);
List<RequestMatcher> matchers = new ArrayList<>();
endpoints.getAllPaths().forEach((path) -> matchers.add(pathMatcher(path + "/**")));
matchers.add(pathMatcher(BASE_PATH));
matchers.add(pathMatcher(BASE_PATH + "/"));
return new OrRequestMatcher(matchers);
private RequestMatcher getRequestMatcher() {
return pathMatcher(BASE_PATH + "/**");
}
private PathPatternRequestMatcher pathMatcher(String path) {

6
module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryWebEndpointServletHandlerMapping.java

@ -81,6 +81,12 @@ class CloudFoundryWebEndpointServletHandlerMapping extends AbstractWebMvcEndpoin @@ -81,6 +81,12 @@ class CloudFoundryWebEndpointServletHandlerMapping extends AbstractWebMvcEndpoin
this.allEndpoints = allEndpoints;
}
@Override
protected void initHandlerMethods() {
super.initHandlerMethods();
registerCatchAllMapping(HttpStatus.FORBIDDEN);
}
@Override
protected ServletWebOperation wrapServletWebOperation(ExposableWebEndpoint endpoint, WebOperation operation,
ServletWebOperation servletWebOperation) {

2
module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfigurationTests.java

@ -194,7 +194,7 @@ class CloudFoundryReactiveActuatorAutoConfigurationTests { @@ -194,7 +194,7 @@ class CloudFoundryReactiveActuatorAutoConfigurationTests {
assertThat(cfBaseWithTrailingSlashRequestMatches).isTrue();
assertThat(cfRequestMatches).isTrue();
assertThat(cfRequestWithAdditionalPathMatches).isTrue();
assertThat(otherCfRequestMatches).isFalse();
assertThat(otherCfRequestMatches).isTrue();
assertThat(otherRequestMatches).isFalse();
otherRequestMatches = filters.get(1)
.matches(MockServerWebExchange.from(MockServerHttpRequest.get("/some-other-path").build()))

6
module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java

@ -212,6 +212,12 @@ class CloudFoundryWebFluxEndpointIntegrationTests { @@ -212,6 +212,12 @@ class CloudFoundryWebFluxEndpointIntegrationTests {
.doesNotExist()));
}
@Test
void unknownEndpointsAreForbidden() {
this.contextRunner.run(withWebTestClient(
(client) -> client.get().uri("/cfApplication/unknown").exchange().expectStatus().isForbidden()));
}
private ContextConsumer<AssertableReactiveWebApplicationContext> withWebTestClient(
Consumer<WebTestClient> clientConsumer) {
return (context) -> {

4
module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryActuatorAutoConfigurationTests.java

@ -192,9 +192,7 @@ class CloudFoundryActuatorAutoConfigurationTests { @@ -192,9 +192,7 @@ class CloudFoundryActuatorAutoConfigurationTests {
testCloudFoundrySecurity(request, BASE_PATH + "/", chain);
testCloudFoundrySecurity(request, BASE_PATH + "/test", chain);
testCloudFoundrySecurity(request, BASE_PATH + "/test/a", chain);
request.setServletPath(BASE_PATH + "/other-path");
request.setRequestURI(BASE_PATH + "/other-path");
assertThat(chain.matches(request)).isFalse();
testCloudFoundrySecurity(request, BASE_PATH + "/other-path", chain);
request.setServletPath("/some-other-path");
request.setRequestURI("/some-other-path");
assertThat(chain.matches(request)).isFalse();

11
module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/servlet/CloudFoundryMvcWebEndpointIntegrationTests.java

@ -201,6 +201,17 @@ class CloudFoundryMvcWebEndpointIntegrationTests { @@ -201,6 +201,17 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
.doesNotExist());
}
@Test
void unknownEndpointsAreForbidden() {
load(TestEndpointConfiguration.class,
(client) -> client.get()
.uri("/cfApplication/unknown")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isForbidden());
}
private void load(Class<?> configuration, Consumer<WebTestClient> clientConsumer) {
BiConsumer<ApplicationContext, WebTestClient> consumer = (context, client) -> clientConsumer.accept(client);
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)

44
module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/actuate/endpoint/web/AbstractWebFluxEndpointHandlerMapping.java

@ -59,6 +59,7 @@ import org.springframework.http.HttpStatus; @@ -59,6 +59,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.ResponseEntity.BodyBuilder;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
@ -106,6 +107,8 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi @@ -106,6 +107,8 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
private final Method handleReadMethod = getHandleReadMethod();
private final Method handleCatchAllMethod = getCatchAllMethod();
private final boolean shouldRegisterLinksMapping;
/**
@ -141,6 +144,12 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi @@ -141,6 +144,12 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
return method;
}
private static Method getCatchAllMethod() {
Method method = ReflectionUtils.findMethod(CatchAllHandler.class, "handle", ServerWebExchange.class);
Assert.state(method != null, "'method' must not be null");
return method;
}
@Override
protected void initHandlerMethods() {
for (ExposableWebEndpoint endpoint : this.endpoints) {
@ -192,6 +201,17 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi @@ -192,6 +201,17 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
return reactiveWebOperation;
}
/**
* Register a "catch all" handler for the rest of actuator namespace, ensuring that
* all requests are handled by this handler mapping.
* @param responseStatus the response status to use for handled requests
*/
protected void registerCatchAllMapping(HttpStatus responseStatus) {
String subPath = this.endpointMapping.createSubPath("/**");
registerMapping(RequestMappingInfo.paths(subPath).build(), new CatchAllHandler(responseStatus),
this.handleCatchAllMethod);
}
private RequestMappingInfo createRequestMappingInfo(WebOperation operation) {
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
String path = this.endpointMapping.createSubPath(predicate.getPath());
@ -524,6 +544,30 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi @@ -524,6 +544,30 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
}
/**
* Catch-all handler that always replies with a fixed HTTP status.
*/
private static final class CatchAllHandler {
private final HttpStatus responseStatus;
CatchAllHandler(HttpStatus responseStatus) {
this.responseStatus = responseStatus;
}
Mono<Void> handle(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(this.responseStatus);
return response.setComplete();
}
@Override
public String toString() {
return "Actuator catch-all endpoint";
}
}
private static class WebFluxEndpointHandlerMethod extends HandlerMethod {
WebFluxEndpointHandlerMethod(Object bean, Method method) {

36
module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AbstractWebMvcEndpointHandlerMapping.java

@ -103,6 +103,8 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin @@ -103,6 +103,8 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin
private final Method handleMethod = getHandleMethod();
private final Method catchAllMethod = getCatchAllMethod();
private RequestMappingInfo.BuilderConfiguration builderConfig = new RequestMappingInfo.BuilderConfiguration();
/**
@ -146,6 +148,12 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin @@ -146,6 +148,12 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin
return method;
}
private static Method getCatchAllMethod() {
Method method = ReflectionUtils.findMethod(CatchAllHandler.class, "handle", HttpServletResponse.class);
Assert.state(method != null, "'method' must not be null");
return method;
}
@Override
public void afterPropertiesSet() {
this.builderConfig = new RequestMappingInfo.BuilderConfiguration();
@ -202,6 +210,17 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin @@ -202,6 +210,17 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin
return servletWebOperation;
}
/**
* Register a "catch all" handler for the rest of actuator namespace, ensuring that
* all requests are handled by this handler mapping.
* @param responseStatus the response status to use for handled requests
*/
protected void registerCatchAllMapping(HttpStatus responseStatus) {
String subPath = this.endpointMapping.createSubPath("/**");
registerMapping(RequestMappingInfo.paths(subPath).options(this.builderConfig).build(),
new CatchAllHandler(responseStatus), this.catchAllMethod);
}
private RequestMappingInfo createRequestMappingInfo(WebOperationRequestPredicate predicate, String path) {
String subPath = this.endpointMapping.createSubPath(path);
List<String> paths = new ArrayList<>();
@ -464,6 +483,23 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin @@ -464,6 +483,23 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin
}
/**
* Catch-all handler that always replies with a fixed HTTP status.
*/
private static final class CatchAllHandler {
private final HttpStatus responseStatus;
CatchAllHandler(HttpStatus responseStatus) {
this.responseStatus = responseStatus;
}
void handle(HttpServletResponse response) {
response.setStatus(this.responseStatus.value());
}
}
/**
* {@link HandlerMethod} subclass for endpoint information logging.
*/

Loading…
Cancel
Save