mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Add support for server.server-header property
Add a `server.server-header` property which can be used to override the `server` header usually sent back automatically by Tomcat/Jetty or Undertow. See https://www.owasp.org/index.php/Securing_tomcat for background. Fixes gh-4461 Closes gh-4504
This commit is contained in:
parent
b2f1355e74
commit
1b81d9f0b5
@ -67,6 +67,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @author Eddú Meléndez
|
||||
* @see EndpointWebMvcAutoConfiguration
|
||||
*/
|
||||
@Configuration
|
||||
@ -187,6 +188,7 @@ public class EndpointWebMvcChildContextConfiguration {
|
||||
container.setContextPath("");
|
||||
// and add the management-specific bits
|
||||
container.setPort(this.managementServerProperties.getPort());
|
||||
container.setServerHeader(this.server.getServerHeader());
|
||||
container.setAddress(this.managementServerProperties.getAddress());
|
||||
container.addErrorPages(new ErrorPage(this.server.getError().getPath()));
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Andy Wilkinson
|
||||
* @author Ivan Sopov
|
||||
* @author Marcos Barbero
|
||||
* @author Eddú Meléndez
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
|
||||
public class ServerProperties
|
||||
@ -116,6 +117,11 @@ public class ServerProperties
|
||||
*/
|
||||
private Boolean useForwardHeaders;
|
||||
|
||||
/**
|
||||
* Value to use for the server header.
|
||||
*/
|
||||
private String serverHeader;
|
||||
|
||||
private Session session = new Session();
|
||||
|
||||
@NestedConfigurationProperty
|
||||
@ -173,6 +179,7 @@ public class ServerProperties
|
||||
if (getCompression() != null) {
|
||||
container.setCompression(getCompression());
|
||||
}
|
||||
container.setServerHeader(getServerHeader());
|
||||
if (container instanceof TomcatEmbeddedServletContainerFactory) {
|
||||
getTomcat().customizeTomcat(this,
|
||||
(TomcatEmbeddedServletContainerFactory) container);
|
||||
@ -304,6 +311,14 @@ public class ServerProperties
|
||||
this.useForwardHeaders = useForwardHeaders;
|
||||
}
|
||||
|
||||
public String getServerHeader() {
|
||||
return this.serverHeader;
|
||||
}
|
||||
|
||||
public void setServerHeader(String serverHeader) {
|
||||
this.serverHeader = serverHeader;
|
||||
}
|
||||
|
||||
protected final boolean getOrDeduceUseForwardHeaders() {
|
||||
if (this.useForwardHeaders != null) {
|
||||
return this.useForwardHeaders;
|
||||
|
@ -50,6 +50,7 @@ import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
@ -65,6 +66,7 @@ import static org.mockito.Mockito.verify;
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @author Eddú Meléndez
|
||||
*/
|
||||
public class ServerPropertiesTests {
|
||||
|
||||
@ -94,6 +96,19 @@ public class ServerPropertiesTests {
|
||||
assertEquals(9000, this.properties.getPort().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerHeaderDefault() throws Exception {
|
||||
assertNull(this.properties.getServerHeader());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerHeader() throws Exception {
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(this.properties, "server");
|
||||
binder.bind(new MutablePropertyValues(
|
||||
Collections.singletonMap("server.server-header", "Custom Server")));
|
||||
assertEquals("Custom Server", this.properties.getServerHeader());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServletPathAsMapping() throws Exception {
|
||||
RelaxedDataBinder binder = new RelaxedDataBinder(this.properties, "server");
|
||||
|
@ -153,6 +153,7 @@ content into your application; rather pick only the properties that you need.
|
||||
server.jsp-servlet.init-parameters.*= # Init parameters used to configure the JSP servlet
|
||||
server.jsp-servlet.registered=true # Whether or not the JSP servlet is registered
|
||||
server.port=8080 # Server HTTP port.
|
||||
server.server-header= # The value sent in the server response header
|
||||
server.servlet-path=/ # Path of the main dispatcher servlet.
|
||||
server.session.cookie.comment= # Comment for the session cookie.
|
||||
server.session.cookie.domain= # Domain for the session cookie.
|
||||
|
@ -1,2 +1,2 @@
|
||||
server.compression.enabled: true
|
||||
server.compression.min-response-size: 1
|
||||
server.compression.min-response-size: 1
|
||||
|
@ -36,6 +36,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Ivan Sopov
|
||||
* @author Eddú Meléndez
|
||||
* @see AbstractEmbeddedServletContainerFactory
|
||||
*/
|
||||
public abstract class AbstractConfigurableEmbeddedServletContainer
|
||||
@ -74,6 +75,8 @@ public abstract class AbstractConfigurableEmbeddedServletContainer
|
||||
|
||||
private Compression compression;
|
||||
|
||||
private String serverHeader;
|
||||
|
||||
/**
|
||||
* Create a new {@link AbstractConfigurableEmbeddedServletContainer} instance.
|
||||
*/
|
||||
@ -314,6 +317,15 @@ public abstract class AbstractConfigurableEmbeddedServletContainer
|
||||
this.compression = compression;
|
||||
}
|
||||
|
||||
public String getServerHeader() {
|
||||
return this.serverHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServerHeader(String serverHeader) {
|
||||
this.serverHeader = serverHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method that can be used by subclasses wishing to combine the specified
|
||||
* {@link ServletContextInitializer} parameters with those defined in this instance.
|
||||
|
@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Eddú Meléndez
|
||||
* @see EmbeddedServletContainerFactory
|
||||
* @see EmbeddedServletContainerCustomizer
|
||||
*/
|
||||
@ -189,4 +190,10 @@ public interface ConfigurableEmbeddedServletContainer {
|
||||
*/
|
||||
void setCompression(Compression compression);
|
||||
|
||||
/**
|
||||
* Sets the server header value.
|
||||
* @param serverHeader the server header value
|
||||
*/
|
||||
void setServerHeader(String serverHeader);
|
||||
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author David Liu
|
||||
* @author Eddú Meléndez
|
||||
* @see JettyEmbeddedServletContainerFactory
|
||||
*/
|
||||
public class JettyEmbeddedServletContainer implements EmbeddedServletContainer {
|
||||
|
@ -27,14 +27,20 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.server.AbstractConnector;
|
||||
import org.eclipse.jetty.server.ConnectionFactory;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
@ -84,6 +90,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Dave Syer
|
||||
* @author Andrey Hihlovskiy
|
||||
* @author Andy Wilkinson
|
||||
* @author Eddú Meléndez
|
||||
* @see #setPort(int)
|
||||
* @see #setConfigurations(Collection)
|
||||
* @see JettyEmbeddedServletContainer
|
||||
@ -138,14 +145,7 @@ public class JettyEmbeddedServletContainerFactory
|
||||
int port = (getPort() >= 0 ? getPort() : 0);
|
||||
Server server = new Server(new InetSocketAddress(getAddress(), port));
|
||||
configureWebAppContext(context, initializers);
|
||||
if (getCompression() != null && getCompression().getEnabled()) {
|
||||
HandlerWrapper gzipHandler = createGzipHandler();
|
||||
gzipHandler.setHandler(context);
|
||||
server.setHandler(gzipHandler);
|
||||
}
|
||||
else {
|
||||
server.setHandler(context);
|
||||
}
|
||||
server.setHandler(addHandlerWrappers(context));
|
||||
this.logger.info("Server initialized with port: " + port);
|
||||
if (getSsl() != null && getSsl().isEnabled()) {
|
||||
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||
@ -163,6 +163,21 @@ public class JettyEmbeddedServletContainerFactory
|
||||
return getJettyEmbeddedServletContainer(server);
|
||||
}
|
||||
|
||||
private Handler addHandlerWrappers(Handler handler) {
|
||||
if (getCompression() != null && getCompression().getEnabled()) {
|
||||
handler = applyWrapper(handler, createGzipHandler());
|
||||
}
|
||||
if (StringUtils.hasText(getServerHeader())) {
|
||||
handler = applyWrapper(handler, new ServerHeaderHandler(getServerHeader()));
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
private Handler applyWrapper(Handler handler, HandlerWrapper wrapper) {
|
||||
wrapper.setHandler(handler);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private HandlerWrapper createGzipHandler() {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
if (ClassUtils.isPresent(GZIP_HANDLER_JETTY_9_2, classLoader)) {
|
||||
@ -709,4 +724,28 @@ public class JettyEmbeddedServletContainerFactory
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link HandlerWrapper} to add a custom {@code server} header.
|
||||
*/
|
||||
private static class ServerHeaderHandler extends HandlerWrapper {
|
||||
|
||||
private static final String SERVER_HEADER = "server";
|
||||
|
||||
private final String value;
|
||||
|
||||
ServerHeaderHandler(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException, ServletException {
|
||||
if (!response.getHeaderNames().contains(SERVER_HEADER)) {
|
||||
response.setHeader(SERVER_HEADER, this.value);
|
||||
}
|
||||
super.handle(target, baseRequest, request, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Brock Mills
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @author Eddú Meléndez
|
||||
* @see #setPort(int)
|
||||
* @see #setContextLifecycleListeners(Collection)
|
||||
* @see TomcatEmbeddedServletContainer
|
||||
@ -248,6 +249,9 @@ public class TomcatEmbeddedServletContainerFactory
|
||||
protected void customizeConnector(Connector connector) {
|
||||
int port = (getPort() >= 0 ? getPort() : 0);
|
||||
connector.setPort(port);
|
||||
if (StringUtils.hasText(this.getServerHeader())) {
|
||||
connector.setAttribute("server", this.getServerHeader());
|
||||
}
|
||||
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
|
||||
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Ivan Sopov
|
||||
* @author Andy Wilkinson
|
||||
* @author Eddú Meléndez
|
||||
* @since 1.2.0
|
||||
* @see UndertowEmbeddedServletContainerFactory
|
||||
*/
|
||||
@ -77,6 +78,8 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
|
||||
|
||||
private final Compression compression;
|
||||
|
||||
private final String serverHeader;
|
||||
|
||||
private Undertow undertow;
|
||||
|
||||
private boolean started = false;
|
||||
@ -89,12 +92,20 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
|
||||
public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager,
|
||||
String contextPath, int port, boolean useForwardHeaders, boolean autoStart,
|
||||
Compression compression) {
|
||||
this(builder, manager, contextPath, port, useForwardHeaders, autoStart,
|
||||
compression, null);
|
||||
}
|
||||
|
||||
public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager,
|
||||
String contextPath, int port, boolean useForwardHeaders, boolean autoStart,
|
||||
Compression compression, String serverHeader) {
|
||||
this.builder = builder;
|
||||
this.manager = manager;
|
||||
this.contextPath = contextPath;
|
||||
this.useForwardHeaders = useForwardHeaders;
|
||||
this.autoStart = autoStart;
|
||||
this.compression = compression;
|
||||
this.serverHeader = serverHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -123,6 +134,9 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
|
||||
if (this.useForwardHeaders) {
|
||||
httpHandler = Handlers.proxyPeerAddress(httpHandler);
|
||||
}
|
||||
if (StringUtils.hasText(this.serverHeader)) {
|
||||
httpHandler = Handlers.header(httpHandler, "Server", this.serverHeader);
|
||||
}
|
||||
this.builder.setHandler(httpHandler);
|
||||
return this.builder.build();
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ import org.springframework.util.ResourceUtils;
|
||||
* @author Ivan Sopov
|
||||
* @author Andy Wilkinson
|
||||
* @author Marcos Barbero
|
||||
* @author Eddú Meléndez
|
||||
* @since 1.2.0
|
||||
* @see UndertowEmbeddedServletContainer
|
||||
*/
|
||||
@ -219,8 +220,7 @@ public class UndertowEmbeddedServletContainerFactory
|
||||
DeploymentManager manager = createDeploymentManager(initializers);
|
||||
int port = getPort();
|
||||
Builder builder = createBuilder(port);
|
||||
return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(),
|
||||
port, this.useForwardHeaders, port >= 0, getCompression());
|
||||
return getUndertowEmbeddedServletContainer(builder, manager, port);
|
||||
}
|
||||
|
||||
private Builder createBuilder(int port) {
|
||||
@ -476,7 +476,8 @@ public class UndertowEmbeddedServletContainerFactory
|
||||
protected UndertowEmbeddedServletContainer getUndertowEmbeddedServletContainer(
|
||||
Builder builder, DeploymentManager manager, int port) {
|
||||
return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(),
|
||||
port, port >= 0, getCompression());
|
||||
port, isUseForwardHeaders(), port >= 0, getCompression(),
|
||||
getServerHeader());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -520,6 +521,10 @@ public class UndertowEmbeddedServletContainerFactory
|
||||
return this.accessLogEnabled;
|
||||
}
|
||||
|
||||
protected final boolean isUseForwardHeaders() {
|
||||
return this.useForwardHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if x-forward-* headers should be processed.
|
||||
* @param useForwardHeaders if x-forward headers should be used
|
||||
|
@ -698,6 +698,17 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
||||
assertThat(rootResource.get(), is(not(nullValue())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customServerHeader() throws Exception {
|
||||
AbstractEmbeddedServletContainerFactory factory = getFactory();
|
||||
factory.setServerHeader("MyServer");
|
||||
this.container = factory
|
||||
.getEmbeddedServletContainer(exampleServletRegistration());
|
||||
this.container.start();
|
||||
ClientHttpResponse response = getClientResponse(getLocalUrl("/hello"));
|
||||
assertThat(response.getHeaders().getFirst("server"), equalTo("MyServer"));
|
||||
}
|
||||
|
||||
private boolean doTestCompression(int contentSize, String[] mimeTypes,
|
||||
String[] excludedUserAgents) throws Exception {
|
||||
String testContent = setUpFactoryForCompression(contentSize, mimeTypes,
|
||||
|
Loading…
Reference in New Issue
Block a user