Skip to content

Commit decaacd

Browse files
committed
Account for application path for Jersey servlet endpoints
Closes gh-14895
1 parent 8560010 commit decaacd

File tree

9 files changed

+386
-42
lines changed

9 files changed

+386
-42
lines changed

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
3030
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
31+
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
3132
import org.springframework.context.ApplicationContext;
3233
import org.springframework.context.annotation.Bean;
3334
import org.springframework.context.annotation.Configuration;
@@ -82,11 +83,21 @@ public ServletEndpointRegistrar servletEndpointRegistrar(
8283
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
8384
public static class JerseyServletEndpointManagementContextConfiguration {
8485

86+
private final ApplicationContext context;
87+
88+
public JerseyServletEndpointManagementContextConfiguration(
89+
ApplicationContext context) {
90+
this.context = context;
91+
}
92+
8593
@Bean
8694
public ServletEndpointRegistrar servletEndpointRegistrar(
8795
WebEndpointProperties properties,
8896
ServletEndpointsSupplier servletEndpointsSupplier) {
89-
return new ServletEndpointRegistrar(properties.getBasePath(),
97+
JerseyApplicationPath jerseyApplicationPath = this.context
98+
.getBean(JerseyApplicationPath.class);
99+
return new ServletEndpointRegistrar(
100+
jerseyApplicationPath.getRelativePath(properties.getBasePath()),
90101
servletEndpointsSupplier.getEndpoints());
91102
}
92103

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public Object getAll() {
156156

157157
}
158158

159-
interface TestPathMappedEndpoint
159+
public interface TestPathMappedEndpoint
160160
extends ExposableEndpoint<Operation>, PathMappedEndpoint {
161161

162162
}

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import org.glassfish.jersey.server.ResourceConfig;
2525
import org.glassfish.jersey.server.model.Resource;
26+
import org.junit.Test;
2627

2728
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
2829
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
@@ -41,12 +42,14 @@
4142
import org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration;
4243
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
4344
import org.springframework.boot.context.properties.EnableConfigurationProperties;
45+
import org.springframework.boot.test.context.FilteredClassLoader;
4446
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
4547
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
4648
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
4749
import org.springframework.context.ApplicationContext;
4850
import org.springframework.context.annotation.Bean;
4951
import org.springframework.context.annotation.Configuration;
52+
import org.springframework.test.web.reactive.server.WebTestClient;
5053

5154
/**
5255
* Integration tests for {@link EndpointRequest} with Jersey.
@@ -60,6 +63,8 @@ public class JerseyEndpointRequestIntegrationTests
6063
protected WebApplicationContextRunner getContextRunner() {
6164
return new WebApplicationContextRunner(
6265
AnnotationConfigServletWebServerApplicationContext::new)
66+
.withClassLoader(new FilteredClassLoader(
67+
"org.springframework.web.servlet.DispatcherServlet"))
6368
.withUserConfiguration(JerseyEndpointConfiguration.class,
6469
SecurityConfiguration.class, BaseConfiguration.class)
6570
.withConfiguration(AutoConfigurations.of(
@@ -70,6 +75,41 @@ protected WebApplicationContextRunner getContextRunner() {
7075
JerseyAutoConfiguration.class));
7176
}
7277

78+
@Test
79+
public void toLinksWhenApplicationPathSetShouldMatch() {
80+
getContextRunner().withPropertyValues("spring.jersey.application-path=/admin")
81+
.run((context) -> {
82+
WebTestClient webTestClient = getWebTestClient(context);
83+
webTestClient.get().uri("/admin/actuator/").exchange().expectStatus()
84+
.isOk();
85+
webTestClient.get().uri("/admin/actuator").exchange().expectStatus()
86+
.isOk();
87+
});
88+
}
89+
90+
@Test
91+
public void toEndpointWhenApplicationPathSetShouldMatch() {
92+
getContextRunner().withPropertyValues("spring.jersey.application-path=/admin")
93+
.run((context) -> {
94+
WebTestClient webTestClient = getWebTestClient(context);
95+
webTestClient.get().uri("/admin/actuator/e1").exchange()
96+
.expectStatus().isOk();
97+
});
98+
}
99+
100+
@Test
101+
public void toAnyEndpointWhenApplicationPathSetShouldMatch() {
102+
getContextRunner().withPropertyValues("spring.jersey.application-path=/admin",
103+
"spring.security.user.password=password").run((context) -> {
104+
WebTestClient webTestClient = getWebTestClient(context);
105+
webTestClient.get().uri("/admin/actuator/e2").exchange()
106+
.expectStatus().isUnauthorized();
107+
webTestClient.get().uri("/admin/actuator/e2")
108+
.header("Authorization", getBasicAuth()).exchange()
109+
.expectStatus().isOk();
110+
});
111+
}
112+
73113
@Configuration
74114
@EnableConfigurationProperties(WebEndpointProperties.class)
75115
static class JerseyEndpointConfiguration {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package org.springframework.boot.autoconfigure.jersey;
1818

19-
import java.util.Arrays;
19+
import java.util.Collections;
2020
import java.util.EnumSet;
2121
import java.util.List;
2222

@@ -53,6 +53,7 @@
5353
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
5454
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
5555
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
56+
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
5657
import org.springframework.boot.context.properties.EnableConfigurationProperties;
5758
import org.springframework.boot.web.servlet.DynamicRegistrationBean;
5859
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -96,8 +97,6 @@ public class JerseyAutoConfiguration implements ServletContextAware {
9697

9798
private final List<ResourceConfigCustomizer> customizers;
9899

99-
private String path;
100-
101100
public JerseyAutoConfiguration(JerseyProperties jersey, ResourceConfig config,
102101
ObjectProvider<List<ResourceConfigCustomizer>> customizers) {
103102
this.jersey = jersey;
@@ -107,16 +106,15 @@ public JerseyAutoConfiguration(JerseyProperties jersey, ResourceConfig config,
107106

108107
@PostConstruct
109108
public void path() {
110-
resolveApplicationPath();
111109
customize();
112110
}
113111

114-
private void resolveApplicationPath() {
112+
private String resolveApplicationPath() {
115113
if (StringUtils.hasLength(this.jersey.getApplicationPath())) {
116-
this.path = parseApplicationPath(this.jersey.getApplicationPath());
114+
return this.jersey.getApplicationPath();
117115
}
118116
else {
119-
this.path = findApplicationPath(AnnotationUtils.findAnnotation(
117+
return findApplicationPath(AnnotationUtils.findAnnotation(
120118
this.config.getApplication().getClass(), ApplicationPath.class));
121119
}
122120
}
@@ -130,6 +128,12 @@ private void customize() {
130128
}
131129
}
132130

131+
@Bean
132+
@ConditionalOnMissingBean
133+
public JerseyApplicationPath jerseyApplicationPath() {
134+
return this::resolveApplicationPath;
135+
}
136+
133137
@Bean
134138
@ConditionalOnMissingBean
135139
public FilterRegistrationBean<RequestContextFilter> requestContextFilter() {
@@ -143,13 +147,15 @@ public FilterRegistrationBean<RequestContextFilter> requestContextFilter() {
143147
@Bean
144148
@ConditionalOnMissingBean(name = "jerseyFilterRegistration")
145149
@ConditionalOnProperty(prefix = "spring.jersey", name = "type", havingValue = "filter")
146-
public FilterRegistrationBean<ServletContainer> jerseyFilterRegistration() {
150+
public FilterRegistrationBean<ServletContainer> jerseyFilterRegistration(
151+
JerseyApplicationPath applicationPath) {
147152
FilterRegistrationBean<ServletContainer> registration = new FilterRegistrationBean<>();
148153
registration.setFilter(new ServletContainer(this.config));
149-
registration.setUrlPatterns(Arrays.asList(this.path));
154+
registration.setUrlPatterns(
155+
Collections.singletonList(applicationPath.getUrlMapping()));
150156
registration.setOrder(this.jersey.getFilter().getOrder());
151157
registration.addInitParameter(ServletProperties.FILTER_CONTEXT_PATH,
152-
stripPattern(this.path));
158+
stripPattern(applicationPath.getPath()));
153159
addInitParameters(registration);
154160
registration.setName("jerseyFilter");
155161
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
@@ -166,9 +172,10 @@ private String stripPattern(String path) {
166172
@Bean
167173
@ConditionalOnMissingBean(name = "jerseyServletRegistration")
168174
@ConditionalOnProperty(prefix = "spring.jersey", name = "type", havingValue = "servlet", matchIfMissing = true)
169-
public ServletRegistrationBean<ServletContainer> jerseyServletRegistration() {
175+
public ServletRegistrationBean<ServletContainer> jerseyServletRegistration(
176+
JerseyApplicationPath applicationPath) {
170177
ServletRegistrationBean<ServletContainer> registration = new ServletRegistrationBean<>(
171-
new ServletContainer(this.config), this.path);
178+
new ServletContainer(this.config), applicationPath.getUrlMapping());
172179
addInitParameters(registration);
173180
registration.setName(getServletRegistrationName());
174181
registration.setLoadOnStartup(this.jersey.getServlet().getLoadOnStartup());
@@ -188,14 +195,7 @@ private static String findApplicationPath(ApplicationPath annotation) {
188195
if (annotation == null) {
189196
return "/*";
190197
}
191-
return parseApplicationPath(annotation.value());
192-
}
193-
194-
private static String parseApplicationPath(String applicationPath) {
195-
if (!applicationPath.startsWith("/")) {
196-
applicationPath = "/" + applicationPath;
197-
}
198-
return applicationPath.equals("/") ? "/*" : applicationPath + "/*";
198+
return annotation.value();
199199
}
200200

201201
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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.autoconfigure.security.servlet;
17+
18+
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
19+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
20+
import org.springframework.security.web.util.matcher.RequestMatcher;
21+
22+
/**
23+
* {@link RequestMatcherProvider} that provides an {@link AntPathRequestMatcher} that can
24+
* be used for Jersey applications.
25+
*
26+
* @author Madhura Bhave
27+
* @since 2.0.7
28+
*/
29+
public class JerseyRequestMatcherProvider implements RequestMatcherProvider {
30+
31+
private final JerseyApplicationPath jerseyApplicationPath;
32+
33+
public JerseyRequestMatcherProvider(JerseyApplicationPath jerseyApplicationPath) {
34+
this.jerseyApplicationPath = jerseyApplicationPath;
35+
}
36+
37+
@Override
38+
public RequestMatcher getRequestMatcher(String pattern) {
39+
return new AntPathRequestMatcher(
40+
this.jerseyApplicationPath.getRelativePath(pattern));
41+
}
42+
43+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/SecurityRequestMatcherProviderAutoConfiguration.java

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

18+
import org.glassfish.jersey.server.ResourceConfig;
19+
1820
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
1921
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
2023
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
24+
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
2125
import org.springframework.context.annotation.Bean;
2226
import org.springframework.context.annotation.Configuration;
2327
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -31,15 +35,36 @@
3135
* @since 2.0.5
3236
*/
3337
@Configuration
34-
@ConditionalOnClass({ RequestMatcher.class, DispatcherServlet.class })
38+
@ConditionalOnClass({ RequestMatcher.class })
3539
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
36-
@ConditionalOnBean(HandlerMappingIntrospector.class)
3740
public class SecurityRequestMatcherProviderAutoConfiguration {
3841

39-
@Bean
40-
public RequestMatcherProvider requestMatcherProvider(
41-
HandlerMappingIntrospector introspector) {
42-
return new MvcRequestMatcherProvider(introspector);
42+
@Configuration
43+
@ConditionalOnClass(DispatcherServlet.class)
44+
@ConditionalOnBean(HandlerMappingIntrospector.class)
45+
public static class MvcRequestMatcherConfiguration {
46+
47+
@Bean
48+
@ConditionalOnClass(DispatcherServlet.class)
49+
public RequestMatcherProvider requestMatcherProvider(
50+
HandlerMappingIntrospector introspector) {
51+
return new MvcRequestMatcherProvider(introspector);
52+
}
53+
54+
}
55+
56+
@Configuration
57+
@ConditionalOnClass(ResourceConfig.class)
58+
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
59+
@ConditionalOnBean(JerseyApplicationPath.class)
60+
public static class JerseyRequestMatcherConfiguration {
61+
62+
@Bean
63+
public RequestMatcherProvider requestMatcherProvider(
64+
JerseyApplicationPath applicationPath) {
65+
return new JerseyRequestMatcherProvider(applicationPath);
66+
}
67+
4368
}
4469

4570
}

0 commit comments

Comments
 (0)