Skip to content

Commit 674f2f5

Browse files
mbhavephilwebb
andcommitted
EndpointRequest should match @ServletEndpoint
This commit also changes the request matcher for MVC endpoints to use an AntPathRequestMatcher instead of an MvcRequestMatcher. The endpoint is always available under the mapped endpoint path and this way the same matcher can be used for both MVC and Jersey. Fixes gh-17912 Co-authored-by: Phillip Webb <[email protected]>
1 parent 21302df commit 674f2f5

File tree

28 files changed

+1247
-303
lines changed

28 files changed

+1247
-303
lines changed
Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,41 +13,48 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.springframework.boot.autoconfigure.security.servlet;
16+
17+
package org.springframework.boot.actuate.autoconfigure.security.servlet;
1718

1819
import org.glassfish.jersey.server.ResourceConfig;
1920

21+
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2022
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2123
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2225
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
2326
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
27+
import org.springframework.boot.autoconfigure.security.servlet.AntPathRequestMatcherProvider;
28+
import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider;
29+
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
2430
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
2531
import org.springframework.context.annotation.Bean;
2632
import org.springframework.context.annotation.Configuration;
2733
import org.springframework.security.web.util.matcher.RequestMatcher;
2834
import org.springframework.web.servlet.DispatcherServlet;
29-
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
3035

3136
/**
32-
* Auto-configuration for {@link RequestMatcherProvider}.
37+
* {@link ManagementContextConfiguration} that configures the appropriate
38+
* {@link RequestMatcherProvider}.
3339
*
3440
* @author Madhura Bhave
35-
* @since 2.0.5
41+
* @since 2.1.8
3642
*/
37-
@Configuration
43+
@ManagementContextConfiguration
3844
@ConditionalOnClass({ RequestMatcher.class })
3945
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
40-
public class SecurityRequestMatcherProviderAutoConfiguration {
46+
public class SecurityRequestMatchersManagementContextConfiguration {
4147

4248
@Configuration
4349
@ConditionalOnClass(DispatcherServlet.class)
44-
@ConditionalOnBean(HandlerMappingIntrospector.class)
50+
@ConditionalOnBean(DispatcherServletPath.class)
4551
public static class MvcRequestMatcherConfiguration {
4652

4753
@Bean
54+
@ConditionalOnMissingBean
4855
@ConditionalOnClass(DispatcherServlet.class)
49-
public RequestMatcherProvider requestMatcherProvider(HandlerMappingIntrospector introspector) {
50-
return new MvcRequestMatcherProvider(introspector);
56+
public RequestMatcherProvider requestMatcherProvider(DispatcherServletPath servletPath) {
57+
return new AntPathRequestMatcherProvider(servletPath::getRelativePath);
5158
}
5259

5360
}
@@ -60,7 +67,7 @@ public static class JerseyRequestMatcherConfiguration {
6067

6168
@Bean
6269
public RequestMatcherProvider requestMatcherProvider(JerseyApplicationPath applicationPath) {
63-
return new JerseyRequestMatcherProvider(applicationPath);
70+
return new AntPathRequestMatcherProvider(applicationPath::getRelativePath);
6471
}
6572

6673
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManag
9494
org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive.WebFluxEndpointManagementContextConfiguration,\
9595
org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration,\
9696
org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration,\
97+
org.springframework.boot.actuate.autoconfigure.security.servlet.SecurityRequestMatchersManagementContextConfiguration,\
9798
org.springframework.boot.actuate.autoconfigure.web.jersey.JerseySameManagementContextConfiguration,\
9899
org.springframework.boot.actuate.autoconfigure.web.jersey.JerseyChildManagementContextConfiguration,\
99100
org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementChildContextConfiguration,\

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,24 @@
1515
*/
1616
package org.springframework.boot.actuate.autoconfigure.security.servlet;
1717

18-
import java.util.ArrayList;
1918
import java.util.Base64;
20-
import java.util.List;
19+
import java.util.function.Supplier;
2120

21+
import org.jolokia.http.AgentServlet;
2222
import org.junit.Test;
2323

24-
import org.springframework.boot.actuate.endpoint.EndpointId;
25-
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
26-
import org.springframework.boot.actuate.endpoint.Operation;
24+
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
25+
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
26+
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
2727
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
2828
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
29-
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
30-
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
29+
import org.springframework.boot.actuate.endpoint.web.EndpointServlet;
30+
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint;
31+
import org.springframework.boot.autoconfigure.AutoConfigurations;
32+
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
3133
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
34+
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
35+
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
3236
import org.springframework.boot.logging.LogLevel;
3337
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
3438
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
@@ -39,18 +43,13 @@
3943
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
4044
import org.springframework.test.web.reactive.server.WebTestClient;
4145

42-
import static org.mockito.BDDMockito.given;
43-
import static org.mockito.Mockito.mock;
44-
4546
/**
4647
* Abstract base class for {@link EndpointRequest} tests.
4748
*
4849
* @author Madhura Bhave
4950
*/
5051
public abstract class AbstractEndpointRequestIntegrationTests {
5152

52-
protected abstract WebApplicationContextRunner getContextRunner();
53-
5453
@Test
5554
public void toEndpointShouldMatch() {
5655
getContextRunner().run((context) -> {
@@ -79,6 +78,17 @@ public void toLinksShouldMatch() {
7978
});
8079
}
8180

81+
protected final WebApplicationContextRunner getContextRunner() {
82+
return createContextRunner().withPropertyValues("management.endpoints.web.exposure.include=*")
83+
.withUserConfiguration(BaseConfiguration.class, SecurityConfiguration.class).withConfiguration(
84+
AutoConfigurations.of(JacksonAutoConfiguration.class, SecurityAutoConfiguration.class,
85+
UserDetailsServiceAutoConfiguration.class, EndpointAutoConfiguration.class,
86+
WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class));
87+
88+
}
89+
90+
protected abstract WebApplicationContextRunner createContextRunner();
91+
8292
protected WebTestClient getWebTestClient(AssertableWebApplicationContext context) {
8393
int port = context.getSourceApplicationContext(AnnotationConfigServletWebServerApplicationContext.class)
8494
.getWebServer().getPort();
@@ -108,19 +118,8 @@ public TestEndpoint3 endpoint3() {
108118
}
109119

110120
@Bean
111-
public PathMappedEndpoints pathMappedEndpoints() {
112-
List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
113-
endpoints.add(mockEndpoint("e1"));
114-
endpoints.add(mockEndpoint("e2"));
115-
endpoints.add(mockEndpoint("e3"));
116-
return new PathMappedEndpoints("/actuator", () -> endpoints);
117-
}
118-
119-
private TestPathMappedEndpoint mockEndpoint(String id) {
120-
TestPathMappedEndpoint endpoint = mock(TestPathMappedEndpoint.class);
121-
given(endpoint.getEndpointId()).willReturn(EndpointId.of(id));
122-
given(endpoint.getRootPath()).willReturn(id);
123-
return endpoint;
121+
public TestServletEndpoint servletEndpoint() {
122+
return new TestServletEndpoint();
124123
}
125124

126125
}
@@ -155,7 +154,13 @@ public Object getAll() {
155154

156155
}
157156

158-
public interface TestPathMappedEndpoint extends ExposableEndpoint<Operation>, PathMappedEndpoint {
157+
@ServletEndpoint(id = "se1")
158+
static class TestServletEndpoint implements Supplier<EndpointServlet> {
159+
160+
@Override
161+
public EndpointServlet get() {
162+
return new EndpointServlet(AgentServlet.class);
163+
}
159164

160165
}
161166

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/JerseyEndpointRequestIntegrationTests.java

Lines changed: 37 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,17 @@
1515
*/
1616
package org.springframework.boot.actuate.autoconfigure.security.servlet;
1717

18-
import java.util.Arrays;
19-
import java.util.Collection;
20-
import java.util.Collections;
21-
import java.util.HashSet;
22-
import java.util.List;
23-
2418
import org.glassfish.jersey.server.ResourceConfig;
25-
import org.glassfish.jersey.server.model.Resource;
2619
import org.junit.Test;
2720

2821
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
29-
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
30-
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
31-
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
32-
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
33-
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
34-
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
35-
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
3622
import org.springframework.boot.autoconfigure.AutoConfigurations;
37-
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
3823
import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration;
39-
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
40-
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
41-
import org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration;
42-
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
4324
import org.springframework.boot.context.properties.EnableConfigurationProperties;
4425
import org.springframework.boot.test.context.FilteredClassLoader;
4526
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
4627
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
4728
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
48-
import org.springframework.context.ApplicationContext;
4929
import org.springframework.context.annotation.Bean;
5030
import org.springframework.context.annotation.Configuration;
5131
import org.springframework.test.web.reactive.server.WebTestClient;
@@ -57,18 +37,6 @@
5737
*/
5838
public class JerseyEndpointRequestIntegrationTests extends AbstractEndpointRequestIntegrationTests {
5939

60-
@Override
61-
protected WebApplicationContextRunner getContextRunner() {
62-
return new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
63-
.withClassLoader(new FilteredClassLoader("org.springframework.web.servlet.DispatcherServlet"))
64-
.withUserConfiguration(JerseyEndpointConfiguration.class, SecurityConfiguration.class,
65-
BaseConfiguration.class)
66-
.withConfiguration(AutoConfigurations.of(SecurityAutoConfiguration.class,
67-
UserDetailsServiceAutoConfiguration.class,
68-
SecurityRequestMatcherProviderAutoConfiguration.class, JacksonAutoConfiguration.class,
69-
JerseyAutoConfiguration.class));
70-
}
71-
7240
@Test
7341
public void toLinksWhenApplicationPathSetShouldMatch() {
7442
getContextRunner().withPropertyValues("spring.jersey.application-path=/admin").run((context) -> {
@@ -98,16 +66,47 @@ public void toAnyEndpointWhenApplicationPathSetShouldMatch() {
9866
});
9967
}
10068

69+
@Test
70+
public void toAnyEndpointShouldMatchServletEndpoint() {
71+
getContextRunner().withPropertyValues("spring.security.user.password=password",
72+
"management.endpoints.web.exposure.include=se1").run((context) -> {
73+
WebTestClient webTestClient = getWebTestClient(context);
74+
webTestClient.get().uri("/actuator/se1").exchange().expectStatus().isUnauthorized();
75+
webTestClient.get().uri("/actuator/se1").header("Authorization", getBasicAuth()).exchange()
76+
.expectStatus().isOk();
77+
webTestClient.get().uri("/actuator/se1/list").exchange().expectStatus().isUnauthorized();
78+
webTestClient.get().uri("/actuator/se1/list").header("Authorization", getBasicAuth()).exchange()
79+
.expectStatus().isOk();
80+
});
81+
}
82+
83+
@Test
84+
public void toAnyEndpointWhenApplicationPathSetShouldMatchServletEndpoint() {
85+
getContextRunner().withPropertyValues("spring.jersey.application-path=/admin",
86+
"spring.security.user.password=password", "management.endpoints.web.exposure.include=se1")
87+
.run((context) -> {
88+
WebTestClient webTestClient = getWebTestClient(context);
89+
webTestClient.get().uri("/admin/actuator/se1").exchange().expectStatus().isUnauthorized();
90+
webTestClient.get().uri("/admin/actuator/se1").header("Authorization", getBasicAuth()).exchange()
91+
.expectStatus().isOk();
92+
webTestClient.get().uri("/admin/actuator/se1/list").exchange().expectStatus().isUnauthorized();
93+
webTestClient.get().uri("/admin/actuator/se1/list").header("Authorization", getBasicAuth())
94+
.exchange().expectStatus().isOk();
95+
});
96+
}
97+
98+
@Override
99+
protected WebApplicationContextRunner createContextRunner() {
100+
return new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
101+
.withClassLoader(new FilteredClassLoader("org.springframework.web.servlet.DispatcherServlet"))
102+
.withUserConfiguration(JerseyEndpointConfiguration.class)
103+
.withConfiguration(AutoConfigurations.of(JerseyAutoConfiguration.class));
104+
}
105+
101106
@Configuration
102107
@EnableConfigurationProperties(WebEndpointProperties.class)
103108
static class JerseyEndpointConfiguration {
104109

105-
private final ApplicationContext applicationContext;
106-
107-
JerseyEndpointConfiguration(ApplicationContext applicationContext) {
108-
this.applicationContext = applicationContext;
109-
}
110-
111110
@Bean
112111
public TomcatServletWebServerFactory tomcat() {
113112
return new TomcatServletWebServerFactory(0);
@@ -118,24 +117,6 @@ public ResourceConfig resourceConfig() {
118117
return new ResourceConfig();
119118
}
120119

121-
@Bean
122-
public ResourceConfigCustomizer webEndpointRegistrar() {
123-
return this::customize;
124-
}
125-
126-
private void customize(ResourceConfig config) {
127-
List<String> mediaTypes = Arrays.asList(javax.ws.rs.core.MediaType.APPLICATION_JSON,
128-
ActuatorMediaType.V2_JSON);
129-
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes, mediaTypes);
130-
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(this.applicationContext,
131-
new ConversionServiceParameterValueMapper(), endpointMediaTypes,
132-
Arrays.asList((id) -> id.toString()), Collections.emptyList(), Collections.emptyList());
133-
Collection<Resource> resources = new JerseyEndpointResourceFactory().createEndpointResources(
134-
new EndpointMapping("/actuator"), discoverer.getEndpoints(), endpointMediaTypes,
135-
new EndpointLinksResolver(discoverer.getEndpoints()));
136-
config.registerResources(new HashSet<>(resources));
137-
}
138-
139120
}
140121

141122
}

0 commit comments

Comments
 (0)