diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc index e012ac6ddcb..09c5dcd2f4c 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc @@ -562,7 +562,7 @@ server: NOTE: You can trust all proxies by setting the `internal-proxies` to empty (but do not do so in production). -TIP: If you are using Tomcat, terminating SSL at the proxy, and have set configprop:server.tomcat.use-relative-redirects[] to `false`, then configprop:server.tomcat.redirect-context-root[] should also be set to `false`. +TIP: If you are using Tomcat, terminating SSL at the proxy, and have set configprop:server.tomcat.servlet.use-relative-redirects[] to `false`, then configprop:server.tomcat.servlet.redirect-context-root[] should also be set to `false`. This allows the `X-Forwarded-Proto` header to be honored before any redirects are performed. When relative redirects are in use, which is Tomcat's default, the context root redirect carries no scheme so there is nothing for the header to correct. diff --git a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerProperties.java b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerProperties.java index 3b9ff9e05aa..4d8a96d89b2 100644 --- a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerProperties.java +++ b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerProperties.java @@ -27,6 +27,7 @@ import java.util.List; import org.jspecify.annotations.Nullable; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.boot.convert.DurationUnit; import org.springframework.util.unit.DataSize; @@ -95,18 +96,6 @@ public class TomcatServerProperties { */ private DataSize maxSwallowSize = DataSize.ofMegabytes(2); - /** - * Whether requests to the context root should be redirected by appending a / to the - * path. When using SSL terminated at a proxy, this property should be set to false. - */ - private Boolean redirectContextRoot = true; - - /** - * Whether HTTP 1.1 and later location headers generated by a call to sendRedirect - * will use relative or absolute redirects. - */ - private boolean useRelativeRedirects = true; - /** * Character encoding to use to decode the URI. */ @@ -145,13 +134,6 @@ public class TomcatServerProperties { */ private int maxKeepAliveRequests = 100; - /** - * List of additional patterns that match jars to ignore for TLD scanning. The special - * '?' and '*' characters can be used in the pattern to match one and only one - * character and zero or more characters respectively. - */ - private List additionalTldSkipPatterns = new ArrayList<>(); - /** * List of additional unencoded characters that should be allowed in URI paths. Only * "< > [ \ ] ^ ` { | }" are allowed. @@ -211,6 +193,11 @@ public class TomcatServerProperties { */ private final Remoteip remoteip = new Remoteip(); + /** + * Servlet-specific configuration. + */ + private final Servlet servlet = new Servlet(); + public Duration getBackgroundProcessorDelay() { return this.backgroundProcessorDelay; } @@ -227,20 +214,26 @@ public class TomcatServerProperties { this.basedir = basedir; } + @Deprecated(since = "4.2.0", forRemoval = true) + @DeprecatedConfigurationProperty(replacement = "server.tomcat.servlet.redirect-context-root", since = "4.2.0") public Boolean getRedirectContextRoot() { - return this.redirectContextRoot; + return this.servlet.isRedirectContextRoot(); } + @Deprecated(since = "4.2.0", forRemoval = true) public void setRedirectContextRoot(Boolean redirectContextRoot) { - this.redirectContextRoot = redirectContextRoot; + this.servlet.setRedirectContextRoot(redirectContextRoot); } + @Deprecated(since = "4.2.0", forRemoval = true) + @DeprecatedConfigurationProperty(replacement = "server.tomcat.servlet.use-relative-redirects", since = "4.2.0") public boolean isUseRelativeRedirects() { - return this.useRelativeRedirects; + return this.servlet.isUseRelativeRedirects(); } + @Deprecated(since = "4.2.0", forRemoval = true) public void setUseRelativeRedirects(boolean useRelativeRedirects) { - this.useRelativeRedirects = useRelativeRedirects; + this.servlet.setUseRelativeRedirects(useRelativeRedirects); } public Charset getUriEncoding() { @@ -299,12 +292,16 @@ public class TomcatServerProperties { this.maxKeepAliveRequests = maxKeepAliveRequests; } + @Deprecated(since = "4.2.0", forRemoval = true) + @DeprecatedConfigurationProperty(replacement = "server.tomcat.servlet.additional-tld-skip-patterns", + since = "4.2.0") public List getAdditionalTldSkipPatterns() { - return this.additionalTldSkipPatterns; + return this.servlet.getAdditionalTldSkipPatterns(); } + @Deprecated(since = "4.2.0", forRemoval = true) public void setAdditionalTldSkipPatterns(List additionalTldSkipPatterns) { - this.additionalTldSkipPatterns = additionalTldSkipPatterns; + this.servlet.setAdditionalTldSkipPatterns(additionalTldSkipPatterns); } public List getRelaxedPathChars() { @@ -399,6 +396,10 @@ public class TomcatServerProperties { return this.remoteip; } + public Servlet getServlet() { + return this.servlet; + } + /** * Tomcat access log properties. */ @@ -846,6 +847,54 @@ public class TomcatServerProperties { } + public static class Servlet { + + /** + * List of additional patterns that match jars to ignore for TLD scanning. The + * special '?' and '*' characters can be used in the pattern to match one and only + * one character and zero or more characters respectively. + */ + private List additionalTldSkipPatterns = new ArrayList<>(); + + /** + * Whether requests to the context root should be redirected by appending a / to + * the path. When using SSL terminated at a proxy, this property should be set to + * false. + */ + private boolean redirectContextRoot = true; + + /** + * Whether HTTP 1.1 and later location headers generated by a call to sendRedirect + * will use relative or absolute redirects. + */ + private boolean useRelativeRedirects = true; + + public List getAdditionalTldSkipPatterns() { + return this.additionalTldSkipPatterns; + } + + public void setAdditionalTldSkipPatterns(List additionalTldSkipPatterns) { + this.additionalTldSkipPatterns = additionalTldSkipPatterns; + } + + public boolean isRedirectContextRoot() { + return this.redirectContextRoot; + } + + public void setRedirectContextRoot(boolean redirectContextRoot) { + this.redirectContextRoot = redirectContextRoot; + } + + public boolean isUseRelativeRedirects() { + return this.useRelativeRedirects; + } + + public void setUseRelativeRedirects(boolean useRelativeRedirects) { + this.useRelativeRedirects = useRelativeRedirects; + } + + } + /** * When to use APR. */ diff --git a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizer.java b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizer.java index 7af0d96e6d3..56a994482b3 100644 --- a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizer.java +++ b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizer.java @@ -18,6 +18,7 @@ package org.springframework.boot.tomcat.autoconfigure.servlet; import org.springframework.boot.tomcat.ConfigurableTomcatWebServerFactory; import org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties; +import org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties.Servlet; import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.core.Ordered; @@ -33,10 +34,10 @@ import org.springframework.util.ObjectUtils; class TomcatServletWebServerFactoryCustomizer implements WebServerFactoryCustomizer, Ordered { - private final TomcatServerProperties tomcatProperties; + private final Servlet tomcatServletProperties; TomcatServletWebServerFactoryCustomizer(TomcatServerProperties tomcatProperties) { - this.tomcatProperties = tomcatProperties; + this.tomcatServletProperties = tomcatProperties.getServlet(); } @Override @@ -46,13 +47,11 @@ class TomcatServletWebServerFactoryCustomizer @Override public void customize(TomcatServletWebServerFactory factory) { - if (!ObjectUtils.isEmpty(this.tomcatProperties.getAdditionalTldSkipPatterns())) { - factory.getTldSkipPatterns().addAll(this.tomcatProperties.getAdditionalTldSkipPatterns()); + if (!ObjectUtils.isEmpty(this.tomcatServletProperties.getAdditionalTldSkipPatterns())) { + factory.getTldSkipPatterns().addAll(this.tomcatServletProperties.getAdditionalTldSkipPatterns()); } - if (this.tomcatProperties.getRedirectContextRoot() != null) { - customizeRedirectContextRoot(factory, this.tomcatProperties.getRedirectContextRoot()); - } - customizeUseRelativeRedirects(factory, this.tomcatProperties.isUseRelativeRedirects()); + customizeRedirectContextRoot(factory, this.tomcatServletProperties.isRedirectContextRoot()); + customizeUseRelativeRedirects(factory, this.tomcatServletProperties.isUseRelativeRedirects()); } private void customizeRedirectContextRoot(ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) { diff --git a/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerPropertiesTests.java b/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerPropertiesTests.java index c7eee4a6fe4..692655c878e 100644 --- a/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerPropertiesTests.java +++ b/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerPropertiesTests.java @@ -74,7 +74,7 @@ class TomcatServerPropertiesTests { map.put("server.tomcat.background-processor-delay", "10"); map.put("server.tomcat.relaxed-path-chars", "|,<"); map.put("server.tomcat.relaxed-query-chars", "^ , | "); - map.put("server.tomcat.use-relative-redirects", "false"); + map.put("server.tomcat.servlet.use-relative-redirects", "false"); bind(map); Accesslog accesslog = this.properties.getAccesslog(); assertThat(accesslog.getConditionIf()).isEqualTo("foo"); @@ -96,7 +96,7 @@ class TomcatServerPropertiesTests { assertThat(this.properties.getBackgroundProcessorDelay()).hasSeconds(10); assertThat(this.properties.getRelaxedPathChars()).containsExactly('|', '<'); assertThat(this.properties.getRelaxedQueryChars()).containsExactly('^', '|'); - assertThat(this.properties.isUseRelativeRedirects()).isFalse(); + assertThat(this.properties.getServlet().isUseRelativeRedirects()).isFalse(); } @Test @@ -213,7 +213,7 @@ class TomcatServerPropertiesTests { @Test void tomcatRedirectContextRootMatchesDefault() { - assertThat(this.properties.getRedirectContextRoot()) + assertThat(this.properties.getServlet().isRedirectContextRoot()) .isEqualTo(new StandardContext().getMapperContextRootRedirectEnabled()); } @@ -237,12 +237,13 @@ class TomcatServerPropertiesTests { @Test void tomcatUseRelativeRedirectsDefaultsToTrue() { - assertThat(this.properties.isUseRelativeRedirects()).isTrue(); + assertThat(this.properties.getServlet().isUseRelativeRedirects()).isTrue(); } @Test void tomcatUseRelativeRedirectsMatchesDefault() { - assertThat(this.properties.isUseRelativeRedirects()).isEqualTo(new StandardContext().getUseRelativeRedirects()); + assertThat(this.properties.getServlet().isUseRelativeRedirects()) + .isEqualTo(new StandardContext().getUseRelativeRedirects()); } @Test diff --git a/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizerTests.java b/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizerTests.java index a1ee752561a..e958ab57e37 100644 --- a/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizerTests.java +++ b/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizerTests.java @@ -54,12 +54,27 @@ class TomcatServletWebServerFactoryCustomizerTests { @Test void customTldSkip() { + bind("server.tomcat.servlet.additional-tld-skip-patterns=foo.jar,bar.jar"); + testCustomTldSkip("foo.jar", "bar.jar"); + } + + @Test + @Deprecated(since = "4.2.0", forRemoval = true) + void customTldSkipUsingDeprecatedProperty() { bind("server.tomcat.additional-tld-skip-patterns=foo.jar,bar.jar"); testCustomTldSkip("foo.jar", "bar.jar"); } @Test void customTldSkipAsList() { + bind("server.tomcat.servlet.additional-tld-skip-patterns[0]=biz.jar", + "server.tomcat.servlet.additional-tld-skip-patterns[1]=bah.jar"); + testCustomTldSkip("biz.jar", "bah.jar"); + } + + @Test + @Deprecated(since = "4.2.0", forRemoval = true) + void customTldSkipAsListUsingDeprecatedProperty() { bind("server.tomcat.additional-tld-skip-patterns[0]=biz.jar", "server.tomcat.additional-tld-skip-patterns[1]=bah.jar"); testCustomTldSkip("biz.jar", "bah.jar"); @@ -73,8 +88,18 @@ class TomcatServletWebServerFactoryCustomizerTests { @Test void redirectContextRootCanBeConfigured() { + bind("server.tomcat.servlet.redirect-context-root=false"); + assertThat(this.tomcatProperties.getServlet().isRedirectContextRoot()).isFalse(); + TomcatWebServer server = customizeAndGetServer(); + Context context = (Context) server.getTomcat().getHost().findChildren()[0]; + assertThat(context.getMapperContextRootRedirectEnabled()).isFalse(); + } + + @Test + @Deprecated(since = "4.2.0", forRemoval = true) + void redirectContextRootCanBeConfiguredUsingDeprecatedProperty() { bind("server.tomcat.redirect-context-root=false"); - assertThat(this.tomcatProperties.getRedirectContextRoot()).isFalse(); + assertThat(this.tomcatProperties.getServlet().isRedirectContextRoot()).isFalse(); TomcatWebServer server = customizeAndGetServer(); Context context = (Context) server.getTomcat().getHost().findChildren()[0]; assertThat(context.getMapperContextRootRedirectEnabled()).isFalse(); @@ -82,7 +107,7 @@ class TomcatServletWebServerFactoryCustomizerTests { @Test void useRelativeRedirectsDefaultsToTrue() { - assertThat(this.tomcatProperties.isUseRelativeRedirects()).isTrue(); + assertThat(this.tomcatProperties.getServlet().isUseRelativeRedirects()).isTrue(); TomcatWebServer server = customizeAndGetServer(); Context context = (Context) server.getTomcat().getHost().findChildren()[0]; assertThat(context.getUseRelativeRedirects()).isTrue(); @@ -90,8 +115,18 @@ class TomcatServletWebServerFactoryCustomizerTests { @Test void useRelativeRedirectsCanBeDisabled() { + bind("server.tomcat.servlet.use-relative-redirects=false"); + assertThat(this.tomcatProperties.getServlet().isUseRelativeRedirects()).isFalse(); + TomcatWebServer server = customizeAndGetServer(); + Context context = (Context) server.getTomcat().getHost().findChildren()[0]; + assertThat(context.getUseRelativeRedirects()).isFalse(); + } + + @Test + @Deprecated(since = "4.2.0", forRemoval = true) + void useRelativeRedirectsCanBeDisabledUsingDeprecatedProperty() { bind("server.tomcat.use-relative-redirects=false"); - assertThat(this.tomcatProperties.isUseRelativeRedirects()).isFalse(); + assertThat(this.tomcatProperties.getServlet().isUseRelativeRedirects()).isFalse(); TomcatWebServer server = customizeAndGetServer(); Context context = (Context) server.getTomcat().getHost().findChildren()[0]; assertThat(context.getUseRelativeRedirects()).isFalse();