Avoid exposing Jetty's WebSocketUpgradeFilter as a bean

When the filter is exposed as a bean (directly or through a
registration bean), it's picked up by the auto-configuration of
MockMvc. This causes a problem as MockMvc does not call init on a
filter before it's used and WebSocketUpgradeFilter fails with a
NullPointerException if its doFilter method is called when its init
method has not been called.

This commit reworks the WebSocket auto-configuration to use a
ServletContextInitalizer to register WebSocketUpgradeFilter rather
than a FilterRegistrationBean. This ensure that the filter is still
registered at the required position in the chain (last filter before
the servlet) while also preventing it from being registered with the
auto-configured MockMvc in tests.

Closes gh-37660
This commit is contained in:
Andy Wilkinson
2023-10-03 16:13:24 +01:00
parent 931584f8af
commit 6353603d63
2 changed files with 49 additions and 44 deletions

View File

@@ -36,6 +36,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
@@ -47,7 +48,9 @@ import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.boot.web.servlet.AbstractFilterRegistrationBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
@@ -104,34 +107,44 @@ class WebSocketServletAutoConfigurationTests {
}
@Test
@SuppressWarnings("rawtypes")
void whenCustomUpgradeFilterRegistrationIsDefinedAutoConfiguredRegistrationOfJettyUpgradeFilterBacksOff() {
new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JettyConfiguration.class,
WebSocketServletAutoConfiguration.JettyWebSocketConfiguration.class))
.withUserConfiguration(CustomUpgradeFilterRegistrationConfiguration.class)
.run((context) -> {
Map<String, FilterRegistrationBean> filterRegistrations = context
.getBeansOfType(FilterRegistrationBean.class);
assertThat(filterRegistrations).containsOnlyKeys("unauthorizedFilter",
"customUpgradeFilterRegistration");
});
@Servlet5ClassPathOverrides
void jettyWebSocketUpgradeFilterIsAddedToServletContext() {
try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(
JettyConfiguration.class, WebSocketServletAutoConfiguration.JettyWebSocketConfiguration.class)) {
assertThat(context.getServletContext().getFilterRegistration(WebSocketUpgradeFilter.class.getName()))
.isNotNull();
}
}
@Test
@SuppressWarnings("rawtypes")
void whenCustomUpgradeFilterIsDefinedAutoConfiguredRegistrationOfJettyUpgradeFilterBacksOff() {
void jettyWebSocketUpgradeFilterIsNotExposedAsABean() {
new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JettyConfiguration.class,
WebSocketServletAutoConfiguration.JettyWebSocketConfiguration.class))
.withUserConfiguration(CustomUpgradeFilterConfiguration.class)
.run((context) -> {
Map<String, FilterRegistrationBean> filterRegistrations = context
.getBeansOfType(FilterRegistrationBean.class);
assertThat(filterRegistrations).containsOnlyKeys("unauthorizedFilter");
Map<String, Filter> filters = context.getBeansOfType(Filter.class);
assertThat(filters.values()).noneMatch(WebSocketUpgradeFilter.class::isInstance);
Map<String, AbstractFilterRegistrationBean> filterRegistrations = context
.getBeansOfType(AbstractFilterRegistrationBean.class);
assertThat(filterRegistrations.values()).extracting(AbstractFilterRegistrationBean::getFilter)
.noneMatch(WebSocketUpgradeFilter.class::isInstance);
});
}
@Test
@Servlet5ClassPathOverrides
void jettyWebSocketUpgradeFilterServletContextInitializerBacksOffWhenBeanWithSameNameIsDefined() {
try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(
JettyConfiguration.class, CustomWebSocketUpgradeFilterServletContextInitializerConfiguration.class,
WebSocketServletAutoConfiguration.JettyWebSocketConfiguration.class)) {
BeanDefinition definition = context.getBeanFactory()
.getBeanDefinition("websocketUpgradeFilterServletContextInitializer");
assertThat(definition.getFactoryBeanName())
.contains("CustomWebSocketUpgradeFilterServletContextInitializerConfiguration");
}
}
static Stream<Arguments> testConfiguration() {
String response = "Tomcat";
return Stream.of(
@@ -196,23 +209,13 @@ class WebSocketServletAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class CustomUpgradeFilterRegistrationConfiguration {
static class CustomWebSocketUpgradeFilterServletContextInitializerConfiguration {
@Bean
FilterRegistrationBean<WebSocketUpgradeFilter> customUpgradeFilterRegistration() {
FilterRegistrationBean<WebSocketUpgradeFilter> registration = new FilterRegistrationBean<>(
new WebSocketUpgradeFilter());
return registration;
}
ServletContextInitializer websocketUpgradeFilterServletContextInitializer() {
return (servletContext) -> {
}
@Configuration(proxyBeanMethods = false)
static class CustomUpgradeFilterConfiguration {
@Bean
WebSocketUpgradeFilter customUpgradeFilter() {
return new WebSocketUpgradeFilter();
};
}
}