diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java index 0d37cb7af00..f46f71f96e9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java @@ -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> 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 diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java index e5a5e0bdf2e..b3f288ba8f9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java @@ -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. diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java index 24b8de40e4e..760df4cc8f5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java @@ -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) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java index 5850e0318dc..bc7422b012b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java @@ -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 imp } private Stream getAdditionalPaths(WebServerNamespace webServerNamespace, AdditionalPathsMapper mapper) { - return mapper.getAdditionalPaths(getEndpointId(), webServerNamespace).stream(); + List additionalPaths = mapper.getAdditionalPaths(getEndpointId(), webServerNamespace); + return (additionalPaths != null) ? additionalPaths.stream() : Stream.empty(); } } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTests.java new file mode 100644 index 00000000000..8ac09309e6b --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/ManagementEndpointConflictSmokeTests.java @@ -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 '/'"); + } + +}