Skip to content

Commit 82a2cbb

Browse files
committed
Merge pull request #45251 from YongGoose
* pr/45251: Polish 'Fail fast when base path and an endpoint mapping are set to '/'' Fail fast when base path and an endpoint mapping are set to '/' Closes gh-45251
2 parents c46d19f + 7dac8ca commit 82a2cbb

File tree

5 files changed

+81
-8
lines changed

5 files changed

+81
-8
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfiguration.java

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
2424
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
2525
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter;
26+
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
2627
import org.springframework.boot.actuate.endpoint.EndpointAccessResolver;
2728
import org.springframework.boot.actuate.endpoint.EndpointFilter;
2829
import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
@@ -33,10 +34,12 @@
3334
import org.springframework.boot.actuate.endpoint.web.AdditionalPathsMapper;
3435
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
3536
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
37+
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
3638
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
3739
import org.springframework.boot.actuate.endpoint.web.PathMapper;
3840
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
3941
import org.springframework.boot.actuate.endpoint.web.WebOperation;
42+
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
4043
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
4144
import org.springframework.boot.autoconfigure.AutoConfiguration;
4245
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -47,13 +50,16 @@
4750
import org.springframework.context.ApplicationContext;
4851
import org.springframework.context.annotation.Bean;
4952
import org.springframework.context.annotation.Configuration;
53+
import org.springframework.util.Assert;
54+
import org.springframework.util.StringUtils;
5055

5156
/**
5257
* {@link EnableAutoConfiguration Auto-configuration} for web {@link Endpoint @Endpoint}
5358
* support.
5459
*
5560
* @author Phillip Webb
5661
* @author Stephane Nicoll
62+
* @author Yongjun Hong
5763
* @since 2.0.0
5864
*/
5965
@AutoConfiguration(after = EndpointAutoConfiguration.class)
@@ -109,7 +115,32 @@ public org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoi
109115
@Bean
110116
@ConditionalOnMissingBean
111117
public PathMappedEndpoints pathMappedEndpoints(Collection<EndpointsSupplier<?>> endpointSuppliers) {
112-
return new PathMappedEndpoints(this.properties.getBasePath(), endpointSuppliers);
118+
String basePath = this.properties.getBasePath();
119+
PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints(basePath, endpointSuppliers);
120+
if ((!StringUtils.hasText(basePath) || "/".equals(basePath))
121+
&& ManagementPortType.get(this.applicationContext.getEnvironment()) == ManagementPortType.SAME) {
122+
assertHasNoRootPaths(pathMappedEndpoints);
123+
}
124+
return pathMappedEndpoints;
125+
}
126+
127+
private void assertHasNoRootPaths(PathMappedEndpoints endpoints) {
128+
for (PathMappedEndpoint endpoint : endpoints) {
129+
if (endpoint instanceof ExposableWebEndpoint webEndpoint) {
130+
Assert.state(!isMappedToRootPath(webEndpoint),
131+
() -> "Management base path and the '" + webEndpoint.getEndpointId()
132+
+ "' actuator endpoint are both mapped to '/' "
133+
+ "on the server port which will block access to other endpoints. "
134+
+ "Please use a different path for management endpoints or map them to a "
135+
+ "dedicated management port.");
136+
}
137+
138+
}
139+
}
140+
141+
private boolean isMappedToRootPath(PathMappedEndpoint endpoint) {
142+
return endpoint.getRootPath().equals("/")
143+
|| endpoint.getAdditionalPaths(WebServerNamespace.SERVER).contains("/");
113144
}
114145

115146
@Bean

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/WebMvcEndpointManagementContextConfiguration.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
1717
package org.springframework.boot.actuate.autoconfigure.web.server;
1818

1919
import org.springframework.beans.factory.SmartInitializingSingleton;
20-
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
2120
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextFactory;
2221
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
2322
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -45,7 +44,7 @@
4544
*/
4645
@AutoConfiguration
4746
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
48-
@EnableConfigurationProperties({ WebEndpointProperties.class, ManagementServerProperties.class })
47+
@EnableConfigurationProperties(ManagementServerProperties.class)
4948
public class ManagementContextAutoConfiguration {
5049

5150
@Configuration(proxyBeanMethods = false)

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -61,7 +61,8 @@ public List<String> getAdditionalPaths(WebServerNamespace webServerNamespace) {
6161
}
6262

6363
private Stream<String> getAdditionalPaths(WebServerNamespace webServerNamespace, AdditionalPathsMapper mapper) {
64-
return mapper.getAdditionalPaths(getEndpointId(), webServerNamespace).stream();
64+
List<String> additionalPaths = mapper.getAdditionalPaths(getEndpointId(), webServerNamespace);
65+
return (additionalPaths != null) ? additionalPaths.stream() : Stream.empty();
6566
}
6667

6768
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package smoketest.actuator;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.factory.BeanCreationException;
22+
import org.springframework.boot.SpringApplication;
23+
24+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
25+
26+
/**
27+
* Verifies that an exception is thrown when management and server endpoint paths
28+
* conflict.
29+
*
30+
* @author Yongjun Hong
31+
*/
32+
class ManagementEndpointConflictSmokeTests {
33+
34+
@Test
35+
void shouldThrowExceptionWhenManagementAndServerPathsConflict() {
36+
assertThatExceptionOfType(BeanCreationException.class)
37+
.isThrownBy(() -> SpringApplication.run(SampleActuatorApplication.class,
38+
"--management.endpoints.web.base-path=/", "--management.endpoints.web.path-mapping.health=/"))
39+
.withMessageContaining("Management base path and the 'health' actuator endpoint are both mapped to '/'");
40+
}
41+
42+
}

0 commit comments

Comments
 (0)