Polish 'Fail fast when base path and an endpoint mapping are set to '/''

See gh-45251
This commit is contained in:
Phillip Webb 2025-04-22 11:48:14 -07:00
parent 8f535b266c
commit 7dac8ca345
6 changed files with 81 additions and 48 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,6 +23,7 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.EndpointAccessResolver;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
@ -33,10 +34,12 @@ import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -47,6 +50,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for web {@link Endpoint @Endpoint}
@ -54,6 +59,7 @@ import org.springframework.context.annotation.Configuration;
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Yongjun Hong
* @since 2.0.0
*/
@AutoConfiguration(after = EndpointAutoConfiguration.class)
@ -109,7 +115,32 @@ public class WebEndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public PathMappedEndpoints pathMappedEndpoints(Collection<EndpointsSupplier<?>> endpointSuppliers) {
return new PathMappedEndpoints(this.properties.getBasePath(), endpointSuppliers);
String basePath = this.properties.getBasePath();
PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints(basePath, endpointSuppliers);
if ((!StringUtils.hasText(basePath) || "/".equals(basePath))
&& ManagementPortType.get(this.applicationContext.getEnvironment()) == ManagementPortType.SAME) {
assertHasNoRootPaths(pathMappedEndpoints);
}
return pathMappedEndpoints;
}
private void assertHasNoRootPaths(PathMappedEndpoints endpoints) {
for (PathMappedEndpoint endpoint : endpoints) {
if (endpoint instanceof ExposableWebEndpoint webEndpoint) {
Assert.state(!isMappedToRootPath(webEndpoint),
() -> "Management base path and the '" + webEndpoint.getEndpointId()
+ "' actuator endpoint are both mapped to '/' "
+ "on the server port which will block access to other endpoints. "
+ "Please use a different path for management endpoints or map them to a "
+ "dedicated management port.");
}
}
}
private boolean isMappedToRootPath(PathMappedEndpoint endpoint) {
return endpoint.getRootPath().equals("/")
|| endpoint.getAdditionalPaths(WebServerNamespace.SERVER).contains("/");
}
@Bean

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -69,7 +69,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
*
* @author Andy Wilkinson
* @author Phillip Webb
* @author Yongjun Hong
* @since 2.0.0
*/
@ManagementContextConfiguration(proxyBeanMethods = false)
@ -94,18 +93,6 @@ public class WebMvcEndpointManagementContextConfiguration {
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
if (basePath.isEmpty() && ManagementPortType.get(environment).equals(ManagementPortType.SAME)) {
for (ExposableWebEndpoint endpoint : webEndpoints) {
if ("/".equals(endpoint.getRootPath())) {
throw new IllegalStateException(
"Management endpoints and endpoint path are both mapped to '/' on the server port which will "
+ "block access to other endpoints. Please use a different path for management endpoints or "
+ "map them to a dedicated management port.");
}
}
}
boolean shouldRegisterLinksMapping = shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,7 +17,6 @@
package org.springframework.boot.actuate.autoconfigure.web.server;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextFactory;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@ -45,7 +44,7 @@ import org.springframework.util.Assert;
*/
@AutoConfiguration
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties({ WebEndpointProperties.class, ManagementServerProperties.class })
@EnableConfigurationProperties(ManagementServerProperties.class)
public class ManagementContextAutoConfiguration {
@Configuration(proxyBeanMethods = false)

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -61,7 +61,8 @@ class DiscoveredWebEndpoint extends AbstractDiscoveredEndpoint<WebOperation> imp
}
private Stream<String> getAdditionalPaths(WebServerNamespace webServerNamespace, AdditionalPathsMapper mapper) {
return mapper.getAdditionalPaths(getEndpointId(), webServerNamespace).stream();
List<String> additionalPaths = mapper.getAdditionalPaths(getEndpointId(), webServerNamespace);
return (additionalPaths != null) ? additionalPaths.stream() : Stream.empty();
}
}

View File

@ -1,27 +0,0 @@
package smoketest.actuator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.SpringApplication;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Verifies that an exception is thrown when management and server endpoint paths
* conflict.
*
* @author Yongjun Hong
*/
class ManagementEndpointConflictSmokeTest {
@Test
void shouldThrowExceptionWhenManagementAndServerPathsConflict() {
assertThatThrownBy(() -> {
SpringApplication.run(SampleActuatorApplication.class, "--management.endpoints.web.base-path=/",
"--management.endpoints.web.path-mapping.health=/");
}).isInstanceOf(BeanCreationException.class)
.hasMessageContaining("Management endpoints and endpoint path are both mapped to '/'");
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package smoketest.actuator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.SpringApplication;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Verifies that an exception is thrown when management and server endpoint paths
* conflict.
*
* @author Yongjun Hong
*/
class ManagementEndpointConflictSmokeTests {
@Test
void shouldThrowExceptionWhenManagementAndServerPathsConflict() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> SpringApplication.run(SampleActuatorApplication.class,
"--management.endpoints.web.base-path=/", "--management.endpoints.web.path-mapping.health=/"))
.withMessageContaining("Management base path and the 'health' actuator endpoint are both mapped to '/'");
}
}