Skip to content

Commit e93f9c3

Browse files
committed
Guard against WebServerApplicationContext not being present
Update security matchers and WebFlux actuator support to guard against the `WebServerApplicationContext` class not being present. Fixes gh-48388
1 parent 1c637d8 commit e93f9c3

File tree

12 files changed

+162
-40
lines changed

12 files changed

+162
-40
lines changed

module/spring-boot-actuator/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ description = "Spring Boot Actuator"
2727
dependencies {
2828
api(project(":core:spring-boot"))
2929

30+
optional(project(":module:spring-boot-web-server"))
3031
optional("com.fasterxml.jackson.core:jackson-databind")
3132
optional("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
3233
optional("com.github.ben-manes.caffeine:caffeine")

module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespace.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
import org.jspecify.annotations.Nullable;
2020

21+
import org.springframework.boot.web.server.context.WebServerApplicationContext;
22+
import org.springframework.context.ApplicationContext;
23+
import org.springframework.util.ClassUtils;
2124
import org.springframework.util.StringUtils;
2225

2326
/**
@@ -30,6 +33,8 @@
3033
*/
3134
public final class WebServerNamespace {
3235

36+
private static final String WEB_SERVER_CONTEXT_CLASS = "org.springframework.boot.web.server.context.WebServerApplicationContext";
37+
3338
/**
3439
* {@link WebServerNamespace} that represents the main server.
3540
*/
@@ -76,6 +81,21 @@ public String toString() {
7681
return this.value;
7782
}
7883

84+
/**
85+
* Factory method to create a new {@link WebServerNamespace} from a value. If the
86+
* context is {@code null} or not a web server context then {@link #SERVER} is
87+
* returned.
88+
* @param context the application context
89+
* @return the web server namespace
90+
* @since 4.0.1
91+
*/
92+
public static WebServerNamespace from(@Nullable ApplicationContext context) {
93+
if (!ClassUtils.isPresent(WEB_SERVER_CONTEXT_CLASS, null)) {
94+
return SERVER;
95+
}
96+
return from(WebServerApplicationContext.getServerNamespace(context));
97+
}
98+
7999
/**
80100
* Factory method to create a new {@link WebServerNamespace} from a value. If the
81101
* value is empty or {@code null} then {@link #SERVER} is returned.

module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespaceTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ void fromWhenValueHasText() {
3535

3636
@Test
3737
void fromWhenValueIsNull() {
38-
assertThat(WebServerNamespace.from(null)).isEqualTo(WebServerNamespace.SERVER);
38+
assertThat(WebServerNamespace.from((String) null)).isEqualTo(WebServerNamespace.SERVER);
3939
}
4040

4141
@Test

module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/EndpointRequest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
3939
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
4040
import org.springframework.boot.security.web.reactive.ApplicationContextServerWebExchangeMatcher;
41-
import org.springframework.boot.web.server.context.WebServerApplicationContext;
4241
import org.springframework.context.ApplicationContext;
4342
import org.springframework.core.annotation.MergedAnnotation;
4443
import org.springframework.core.annotation.MergedAnnotations;
@@ -221,14 +220,14 @@ protected boolean ignoreApplicationContext(@Nullable ApplicationContext applicat
221220

222221
protected final boolean hasWebServerNamespace(@Nullable ApplicationContext applicationContext,
223222
WebServerNamespace webServerNamespace) {
224-
return WebServerApplicationContext.hasServerNamespace(applicationContext, webServerNamespace.getValue())
223+
return hasServerNamespace(applicationContext, webServerNamespace.getValue())
225224
|| hasImplicitServerNamespace(applicationContext, webServerNamespace);
226225
}
227226

228227
private boolean hasImplicitServerNamespace(@Nullable ApplicationContext applicationContext,
229228
WebServerNamespace webServerNamespace) {
230229
return WebServerNamespace.SERVER.equals(webServerNamespace)
231-
&& WebServerApplicationContext.getServerNamespace(applicationContext) == null
230+
&& getServerNamespace(applicationContext) == null
232231
&& getApplicationContextParent(applicationContext) == null;
233232
}
234233

module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/EndpointRequest.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
3939
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
4040
import org.springframework.boot.security.web.servlet.ApplicationContextRequestMatcher;
41-
import org.springframework.boot.web.server.context.WebServerApplicationContext;
4241
import org.springframework.context.ApplicationContext;
4342
import org.springframework.core.annotation.MergedAnnotation;
4443
import org.springframework.core.annotation.MergedAnnotations;
@@ -183,15 +182,14 @@ protected boolean ignoreApplicationContext(WebApplicationContext applicationCont
183182

184183
protected final boolean hasWebServerNamespace(ApplicationContext applicationContext,
185184
WebServerNamespace webServerNamespace) {
186-
return WebServerApplicationContext.hasServerNamespace(applicationContext, webServerNamespace.getValue())
185+
return hasServerNamespace(applicationContext, webServerNamespace.getValue())
187186
|| hasImplicitServerNamespace(applicationContext, webServerNamespace);
188187
}
189188

190189
private boolean hasImplicitServerNamespace(ApplicationContext applicationContext,
191190
WebServerNamespace webServerNamespace) {
192191
return WebServerNamespace.SERVER.equals(webServerNamespace)
193-
&& WebServerApplicationContext.getServerNamespace(applicationContext) == null
194-
&& applicationContext.getParent() == null;
192+
&& getServerNamespace(applicationContext) == null && applicationContext.getParent() == null;
195193
}
196194

197195
@Override

module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/PathRequest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.springframework.boot.h2console.autoconfigure.H2ConsoleProperties;
2525
import org.springframework.boot.security.autoconfigure.web.StaticResourceLocation;
2626
import org.springframework.boot.security.web.servlet.ApplicationContextRequestMatcher;
27-
import org.springframework.boot.web.server.context.WebServerApplicationContext;
2827
import org.springframework.context.ApplicationContext;
2928
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
3029
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -76,7 +75,7 @@ private H2ConsoleRequestMatcher() {
7675

7776
@Override
7877
protected boolean ignoreApplicationContext(WebApplicationContext applicationContext) {
79-
return WebServerApplicationContext.hasServerNamespace(applicationContext, "management");
78+
return hasServerNamespace(applicationContext, "management");
8079
}
8180

8281
@Override

module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/StaticResourceRequest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
import org.springframework.boot.security.autoconfigure.web.StaticResourceLocation;
2929
import org.springframework.boot.security.web.servlet.ApplicationContextRequestMatcher;
30-
import org.springframework.boot.web.server.context.WebServerApplicationContext;
3130
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath;
3231
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
3332
import org.springframework.security.web.util.matcher.OrRequestMatcher;
@@ -148,7 +147,7 @@ private Stream<String> getPatterns(DispatcherServletPath dispatcherServletPath)
148147

149148
@Override
150149
protected boolean ignoreApplicationContext(WebApplicationContext applicationContext) {
151-
return WebServerApplicationContext.hasServerNamespace(applicationContext, "management");
150+
return hasServerNamespace(applicationContext, "management");
152151
}
153152

154153
@Override

module/spring-boot-security/src/main/java/org/springframework/boot/security/web/reactive/ApplicationContextServerWebExchangeMatcher.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
import reactor.core.publisher.Mono;
2323

2424
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
25+
import org.springframework.boot.web.server.context.WebServerApplicationContext;
2526
import org.springframework.context.ApplicationContext;
2627
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
2728
import org.springframework.util.Assert;
29+
import org.springframework.util.ClassUtils;
2830
import org.springframework.web.server.ServerWebExchange;
2931

3032
/**
@@ -41,6 +43,8 @@
4143
*/
4244
public abstract class ApplicationContextServerWebExchangeMatcher<C> implements ServerWebExchangeMatcher {
4345

46+
private static final String WEB_SERVER_CONTEXT_CLASS = "org.springframework.boot.web.server.context.WebServerApplicationContext";
47+
4448
private final Class<? extends C> contextClass;
4549

4650
private volatile @Nullable Supplier<C> context;
@@ -111,4 +115,34 @@ private Supplier<C> createContext(ServerWebExchange exchange) {
111115
return () -> context.getBean(this.contextClass);
112116
}
113117

118+
/**
119+
* Returns {@code true} if the specified context is a
120+
* {@link WebServerApplicationContext} with a matching server namespace.
121+
* @param context the context to check
122+
* @param serverNamespace the server namespace to match against
123+
* @return {@code true} if the server namespace of the context matches
124+
* @since 4.0.1
125+
*/
126+
protected final boolean hasServerNamespace(@Nullable ApplicationContext context, String serverNamespace) {
127+
if (!ClassUtils.isPresent(WEB_SERVER_CONTEXT_CLASS, null)) {
128+
return false;
129+
}
130+
return WebServerApplicationContext.hasServerNamespace(context, serverNamespace);
131+
}
132+
133+
/**
134+
* Returns the server namespace if the specified context is a
135+
* {@link WebServerApplicationContext}.
136+
* @param context the context
137+
* @return the server namespace or {@code null} if the context is not a
138+
* {@link WebServerApplicationContext}
139+
* @since 4.0.1
140+
*/
141+
protected final @Nullable String getServerNamespace(@Nullable ApplicationContext context) {
142+
if (!ClassUtils.isPresent(WEB_SERVER_CONTEXT_CLASS, null)) {
143+
return null;
144+
}
145+
return WebServerApplicationContext.getServerNamespace(context);
146+
}
147+
114148
}

module/spring-boot-security/src/main/java/org/springframework/boot/security/web/servlet/ApplicationContextRequestMatcher.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
import java.util.function.Supplier;
2020

2121
import jakarta.servlet.http.HttpServletRequest;
22+
import org.jspecify.annotations.Nullable;
2223

2324
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
25+
import org.springframework.boot.web.server.context.WebServerApplicationContext;
2426
import org.springframework.context.ApplicationContext;
2527
import org.springframework.security.web.util.matcher.RequestMatcher;
2628
import org.springframework.util.Assert;
29+
import org.springframework.util.ClassUtils;
2730
import org.springframework.web.context.WebApplicationContext;
2831
import org.springframework.web.context.support.WebApplicationContextUtils;
2932

@@ -41,6 +44,8 @@
4144
*/
4245
public abstract class ApplicationContextRequestMatcher<C> implements RequestMatcher {
4346

47+
private static final String WEB_SERVER_CONTEXT_CLASS = "org.springframework.boot.web.server.context.WebServerApplicationContext";
48+
4449
private final Class<? extends C> contextClass;
4550

4651
private volatile boolean initialized;
@@ -110,4 +115,34 @@ protected void initialized(Supplier<C> context) {
110115
*/
111116
protected abstract boolean matches(HttpServletRequest request, Supplier<C> context);
112117

118+
/**
119+
* Returns {@code true} if the specified context is a
120+
* {@link WebServerApplicationContext} with a matching server namespace.
121+
* @param context the context to check
122+
* @param serverNamespace the server namespace to match against
123+
* @return {@code true} if the server namespace of the context matches
124+
* @since 4.0.1
125+
*/
126+
protected final boolean hasServerNamespace(@Nullable ApplicationContext context, String serverNamespace) {
127+
if (!ClassUtils.isPresent(WEB_SERVER_CONTEXT_CLASS, null)) {
128+
return false;
129+
}
130+
return WebServerApplicationContext.hasServerNamespace(context, serverNamespace);
131+
}
132+
133+
/**
134+
* Returns the server namespace if the specified context is a
135+
* {@link WebServerApplicationContext}.
136+
* @param context the context
137+
* @return the server namespace or {@code null} if the context is not a
138+
* {@link WebServerApplicationContext}
139+
* @since 4.0.1
140+
*/
141+
protected final @Nullable String getServerNamespace(@Nullable ApplicationContext context) {
142+
if (!ClassUtils.isPresent(WEB_SERVER_CONTEXT_CLASS, null)) {
143+
return null;
144+
}
145+
return WebServerApplicationContext.getServerNamespace(context);
146+
}
147+
113148
}

module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/web/servlet/PathRequestTests.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,19 @@
2222
import org.junit.jupiter.api.Test;
2323

2424
import org.springframework.boot.h2console.autoconfigure.H2ConsoleProperties;
25+
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
2526
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
27+
import org.springframework.boot.web.server.context.WebServerApplicationContext;
28+
import org.springframework.context.support.GenericApplicationContext;
2629
import org.springframework.mock.web.MockHttpServletRequest;
2730
import org.springframework.mock.web.MockServletContext;
2831
import org.springframework.security.web.util.matcher.RequestMatcher;
2932
import org.springframework.util.StringUtils;
3033
import org.springframework.web.context.WebApplicationContext;
34+
import org.springframework.web.context.support.StaticWebApplicationContext;
3135

3236
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
3338

3439
/**
3540
* Tests for {@link PathRequest}.
@@ -59,14 +64,31 @@ void toH2ConsoleWhenManagementContextShouldNeverMatch() {
5964
assertMatcher(matcher, "management").doesNotMatch("/js/file.js");
6065
}
6166

67+
@Test
68+
@ClassPathExclusions(packages = "org.springframework.boot.web.server.context")
69+
void toH2ConsoleWhenNoWebServerContextClassPresent() {
70+
assertThatExceptionOfType(NoClassDefFoundError.class)
71+
.isThrownBy(() -> WebServerApplicationContext.class.getName());
72+
RequestMatcher matcher = PathRequest.toH2Console();
73+
StaticWebApplicationContext context = new StaticWebApplicationContext();
74+
assertMatcher(matcher, context).matches("/h2-console");
75+
assertMatcher(matcher, context).matches("/h2-console/subpath");
76+
assertMatcher(matcher, context).doesNotMatch("/js/file.js");
77+
}
78+
6279
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
63-
return assertMatcher(matcher, null);
80+
return assertMatcher(matcher, (String) null);
6481
}
6582

6683
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, @Nullable String serverNamespace) {
67-
TestWebApplicationContext context = new TestWebApplicationContext(serverNamespace);
68-
context.registerBean(ServerProperties.class);
69-
context.registerBean(H2ConsoleProperties.class);
84+
StaticWebApplicationContext context = new TestWebApplicationContext(serverNamespace);
85+
return assertMatcher(matcher, context);
86+
}
87+
88+
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, WebApplicationContext context) {
89+
GenericApplicationContext genericContext = (GenericApplicationContext) context;
90+
genericContext.registerBean(ServerProperties.class);
91+
genericContext.registerBean(H2ConsoleProperties.class);
7092
return assertThat(new RequestMatcherAssert(context, matcher));
7193
}
7294

0 commit comments

Comments
 (0)