Move Servlet-specific Tomcat properties to server.tomcat.servlet

Closes gh-51603
This commit is contained in:
Andy Wilkinson
2026-09-07 09:58:08 +01:00
parent d265e67685
commit f50c8311b9
5 changed files with 126 additions and 42 deletions
@@ -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.
@@ -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<String> 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<String> getAdditionalTldSkipPatterns() {
return this.additionalTldSkipPatterns;
return this.servlet.getAdditionalTldSkipPatterns();
}
@Deprecated(since = "4.2.0", forRemoval = true)
public void setAdditionalTldSkipPatterns(List<String> additionalTldSkipPatterns) {
this.additionalTldSkipPatterns = additionalTldSkipPatterns;
this.servlet.setAdditionalTldSkipPatterns(additionalTldSkipPatterns);
}
public List<Character> 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<String> 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<String> getAdditionalTldSkipPatterns() {
return this.additionalTldSkipPatterns;
}
public void setAdditionalTldSkipPatterns(List<String> 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.
*/
@@ -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<TomcatServletWebServerFactory>, 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) {
@@ -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
@@ -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();