Skip to content

Commit 0fd873f

Browse files
mbhavewilkinsona
authored andcommitted
Servlet path not explicitly required for EndpointRequest
1 parent 85f2db3 commit 0fd873f

File tree

4 files changed

+434
-19
lines changed

4 files changed

+434
-19
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
3636
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
3737
import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider;
38-
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
3938
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
4039
import org.springframework.core.annotation.AnnotatedElementUtils;
4140
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -139,23 +138,13 @@ protected final boolean matches(HttpServletRequest request,
139138

140139
private RequestMatcher createDelegate(WebApplicationContext context) {
141140
try {
142-
String pathPrefix = getPathPrefix(context);
143-
return createDelegate(context, new RequestMatcherFactory(pathPrefix));
141+
return createDelegate(context, new RequestMatcherFactory());
144142
}
145143
catch (NoSuchBeanDefinitionException ex) {
146144
return EMPTY_MATCHER;
147145
}
148146
}
149147

150-
private String getPathPrefix(WebApplicationContext context) {
151-
try {
152-
return context.getBean(DispatcherServletPath.class).getPrefix();
153-
}
154-
catch (NoSuchBeanDefinitionException ex) {
155-
return "";
156-
}
157-
}
158-
159148
protected abstract RequestMatcher createDelegate(WebApplicationContext context,
160149
RequestMatcherFactory requestMatcherFactory);
161150

@@ -313,15 +302,9 @@ protected RequestMatcher createDelegate(WebApplicationContext context,
313302
*/
314303
private static class RequestMatcherFactory {
315304

316-
private final String prefix;
317-
318-
RequestMatcherFactory(String prefix) {
319-
this.prefix = prefix;
320-
}
321-
322305
public RequestMatcher antPath(RequestMatcherProvider matcherProvider,
323306
String... parts) {
324-
StringBuilder pattern = new StringBuilder(this.prefix);
307+
StringBuilder pattern = new StringBuilder();
325308
for (String part : parts) {
326309
pattern.append(part);
327310
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright 2012-2018 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+
* http://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+
package org.springframework.boot.actuate.autoconfigure.security.servlet;
17+
18+
import java.util.ArrayList;
19+
import java.util.Base64;
20+
import java.util.List;
21+
22+
import org.junit.Test;
23+
24+
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
25+
import org.springframework.boot.actuate.endpoint.Operation;
26+
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
27+
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
28+
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
29+
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
30+
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
31+
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
32+
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Configuration;
35+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
36+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
37+
import org.springframework.test.web.reactive.server.WebTestClient;
38+
39+
import static org.mockito.BDDMockito.given;
40+
import static org.mockito.Mockito.mock;
41+
42+
/**
43+
* Abstract base class for {@link EndpointRequest} tests.
44+
*
45+
* @author Madhura Bhave
46+
*/
47+
public abstract class AbstractEndpointRequestIntegrationTests {
48+
49+
protected abstract WebApplicationContextRunner getContextRunner();
50+
51+
@Test
52+
public void toEndpointShouldMatch() {
53+
getContextRunner().run((context) -> {
54+
WebTestClient webTestClient = getWebTestClient(context);
55+
webTestClient.get().uri("/actuator/e1").exchange().expectStatus().isOk();
56+
});
57+
}
58+
59+
@Test
60+
public void toAllEndpointsShouldMatch() {
61+
getContextRunner().withPropertyValues("spring.security.user.password=password")
62+
.run((context) -> {
63+
WebTestClient webTestClient = getWebTestClient(context);
64+
webTestClient.get().uri("/actuator/e2").exchange().expectStatus()
65+
.isUnauthorized();
66+
webTestClient.get().uri("/actuator/e2")
67+
.header("Authorization", getBasicAuth()).exchange()
68+
.expectStatus().isOk();
69+
});
70+
}
71+
72+
@Test
73+
public void toLinksShouldMatch() {
74+
getContextRunner().run((context) -> {
75+
WebTestClient webTestClient = getWebTestClient(context);
76+
webTestClient.get().uri("/actuator").exchange().expectStatus().isOk();
77+
webTestClient.get().uri("/actuator/").exchange().expectStatus().isOk();
78+
});
79+
}
80+
81+
protected WebTestClient getWebTestClient(AssertableWebApplicationContext context) {
82+
int port = context
83+
.getSourceApplicationContext(
84+
AnnotationConfigServletWebServerApplicationContext.class)
85+
.getWebServer().getPort();
86+
return WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build();
87+
}
88+
89+
String getBasicAuth() {
90+
return "Basic " + Base64.getEncoder().encodeToString("user:password".getBytes());
91+
}
92+
93+
static class BaseConfiguration {
94+
95+
@Bean
96+
public TestEndpoint1 endpoint1() {
97+
return new TestEndpoint1();
98+
}
99+
100+
@Bean
101+
public TestEndpoint2 endpoint2() {
102+
return new TestEndpoint2();
103+
}
104+
105+
@Bean
106+
public TestEndpoint3 endpoint3() {
107+
return new TestEndpoint3();
108+
}
109+
110+
@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.getId()).willReturn(id);
122+
given(endpoint.getRootPath()).willReturn(id);
123+
return endpoint;
124+
}
125+
126+
}
127+
128+
@Endpoint(id = "e1")
129+
static class TestEndpoint1 {
130+
131+
@ReadOperation
132+
public Object getAll() {
133+
return "endpoint 1";
134+
}
135+
136+
}
137+
138+
@Endpoint(id = "e2")
139+
static class TestEndpoint2 {
140+
141+
@ReadOperation
142+
public Object getAll() {
143+
return "endpoint 2";
144+
}
145+
146+
}
147+
148+
@Endpoint(id = "e3")
149+
static class TestEndpoint3 {
150+
151+
@ReadOperation
152+
public Object getAll() {
153+
return null;
154+
}
155+
156+
}
157+
158+
interface TestPathMappedEndpoint
159+
extends ExposableEndpoint<Operation>, PathMappedEndpoint {
160+
161+
}
162+
163+
@Configuration
164+
static class SecurityConfiguration {
165+
166+
@Bean
167+
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
168+
return new WebSecurityConfigurerAdapter() {
169+
@Override
170+
protected void configure(HttpSecurity http) throws Exception {
171+
http.authorizeRequests().requestMatchers(EndpointRequest.toLinks())
172+
.permitAll()
173+
.requestMatchers(EndpointRequest.to(TestEndpoint1.class))
174+
.permitAll().requestMatchers(EndpointRequest.toAnyEndpoint())
175+
.authenticated().anyRequest().hasRole("ADMIN").and()
176+
.httpBasic();
177+
}
178+
};
179+
}
180+
181+
}
182+
183+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2012-2018 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+
* http://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+
package org.springframework.boot.actuate.autoconfigure.security.servlet;
17+
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+
24+
import org.glassfish.jersey.server.ResourceConfig;
25+
import org.glassfish.jersey.server.model.Resource;
26+
27+
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
28+
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
29+
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
30+
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
31+
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
32+
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
33+
import org.springframework.boot.actuate.endpoint.web.PathMapper;
34+
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
35+
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
36+
import org.springframework.boot.autoconfigure.AutoConfigurations;
37+
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
38+
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;
43+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
44+
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
45+
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
46+
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
47+
import org.springframework.context.ApplicationContext;
48+
import org.springframework.context.annotation.Bean;
49+
import org.springframework.context.annotation.Configuration;
50+
51+
/**
52+
* Integration tests for {@link EndpointRequest} with Jersey.
53+
*
54+
* @author Madhura Bhave
55+
*/
56+
public class JerseyEndpointRequestIntegrationTests
57+
extends AbstractEndpointRequestIntegrationTests {
58+
59+
@Override
60+
protected WebApplicationContextRunner getContextRunner() {
61+
return new WebApplicationContextRunner(
62+
AnnotationConfigServletWebServerApplicationContext::new)
63+
.withUserConfiguration(JerseyEndpointConfiguration.class,
64+
SecurityConfiguration.class, BaseConfiguration.class)
65+
.withConfiguration(AutoConfigurations.of(
66+
SecurityAutoConfiguration.class,
67+
UserDetailsServiceAutoConfiguration.class,
68+
SecurityRequestMatcherProviderAutoConfiguration.class,
69+
JacksonAutoConfiguration.class,
70+
JerseyAutoConfiguration.class));
71+
}
72+
73+
@Configuration
74+
@EnableConfigurationProperties(WebEndpointProperties.class)
75+
static class JerseyEndpointConfiguration {
76+
77+
private final ApplicationContext applicationContext;
78+
79+
JerseyEndpointConfiguration(ApplicationContext applicationContext) {
80+
this.applicationContext = applicationContext;
81+
}
82+
83+
@Bean
84+
public TomcatServletWebServerFactory tomcat() {
85+
return new TomcatServletWebServerFactory(0);
86+
}
87+
88+
@Bean
89+
public ResourceConfig resourceConfig() {
90+
return new ResourceConfig();
91+
}
92+
93+
@Bean
94+
public ResourceConfigCustomizer webEndpointRegistrar() {
95+
return this::customize;
96+
}
97+
98+
private void customize(ResourceConfig config) {
99+
List<String> mediaTypes = Arrays.asList(
100+
javax.ws.rs.core.MediaType.APPLICATION_JSON,
101+
ActuatorMediaType.V2_JSON);
102+
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes,
103+
mediaTypes);
104+
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(
105+
this.applicationContext, new ConversionServiceParameterValueMapper(),
106+
endpointMediaTypes, PathMapper.useEndpointId(),
107+
Collections.emptyList(), Collections.emptyList());
108+
Collection<Resource> resources = new JerseyEndpointResourceFactory()
109+
.createEndpointResources(new EndpointMapping("/actuator"),
110+
discoverer.getEndpoints(), endpointMediaTypes,
111+
new EndpointLinksResolver(discoverer.getEndpoints()));
112+
config.registerResources(new HashSet<>(resources));
113+
}
114+
115+
}
116+
117+
}

0 commit comments

Comments
 (0)