如何配置Spring Boot以运行HTTPS / HTTP端口

61

Spring Boot有一些属性来配置Web端口和SSL设置,但是一旦设置了SSL证书,HTTP端口就会变成HTTPS端口。

那么,我该如何保持两个端口同时运行,比如80和443?

正如您所看到的,只有一个端口的属性,在这种情况下,“server.ssl”被启用,这使得HTTP端口自动禁用。

##############
### Server ###
##############
server.port=9043
server.session-timeout=1800
server.ssl.key-store=file:///C:/Temp/config/localhost.jks
server.ssl.key-store-password=localhost
server.ssl.key-password=localhost
server.ssl.trust-store=file:///C:/Temp/config/localhost.jks
server.ssl.trust-store-password=localhost

我正在尝试使用Tomcat或Undertow,希望能得到任何帮助!


1
你为什么想要同时运行两个?一条线路要么是安全的,要么是不安全的,但它不应该同时具备两者。 - Makoto
3
我的项目有一些部分需要保护,而另一些则不需要。正如您所知,Https流量比Http慢,因此我想像在任何应用程序服务器中一样同时交换两个协议。 - Carlos Alberto
1
@Makoto - 不一定。 一个端口可以同时支持HTTP和HTTPS。例如 - http://bayou.io/release/0.9/docs/http/Server_SSL_Configuration.html - ZhongYu
@ElLordCode - 我不熟悉Boot;所以你的意思是它只能有一个端口吗? - ZhongYu
当我在Spring Boot中启用SSL设置时,如果我尝试导航到Http端口,将会出现“未知主机”错误,即使使用相同的端口号。 - Carlos Alberto
6个回答

61

目前被接受的答案完美地工作,但如果你想让它在 Spring Boot 2.0.0 及其以后版本中工作,需要进行一些调整:

@Component
public class HttpServer {
  @Bean
  public ServletWebServerFactory servletContainer(@Value("${server.http.port}") int httpPort) {
      Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
      connector.setPort(httpPort);

      TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
      tomcat.addAdditionalTomcatConnectors(connector);
      return tomcat;
  }
}

或者使用Kotlin版本:

@Component
class HttpServer {
  @Bean
  fun servletContainer(@Value("\${server.http.port}") httpPort: Int): ServletWebServerFactory {
    val connector = Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL)
    connector.setPort(httpPort)

    val tomcat = TomcatServletWebServerFactory()
    tomcat.addAdditionalTomcatConnectors(connector)
    return tomcat
  }
}

3
在最新版本的Spring Boot上运行完美无误。 - user666
2
这对我也起作用了。只想澄清一下,这个类替换了被接受答案中的 TomcatConfig 类。此外,我关于调整 yaml 属性的评论仍然适用。 - AForsberg
1
在Spring Boot 2.4.3版本中测试过,支持HTTP/HTTPS,运行良好。 - catch23
Connector类的导入是什么? - Anurag Bhalekar

58

使用属性配置Spring Boot时,只允许配置一个连接器。您需要的是多个连接器,因此必须编写一个 Configuration 类。请按照说明操作。

您可以在下面找到一个工作示例,通过属性配置HTTPS,然后通过 EmbeddedServletContainerCustomizer 配置HTTP。

http://izeye.blogspot.com/2015/01/configure-http-and-https-in-spring-boot.html?showComment=1461632100718#c4988529876932015554

server:
  port: 8080
  ssl:
    enabled: true
    keyStoreType: PKCS12
    key-store: /path/to/keystore.p12
    key-store-password: password
  http:
    port: 8079

@Configuration
public class TomcatConfig {

@Value("${server.http.port}")
private int httpPort;

@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
    return new EmbeddedServletContainerCustomizer() {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                TomcatEmbeddedServletContainerFactory containerFactory =
                        (TomcatEmbeddedServletContainerFactory) container;

                Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);
                connector.setPort(httpPort);
                containerFactory.addAdditionalTomcatConnectors(connector);
            }
        }
    };
}
}

server.http.port 似乎不再存在。 - Stefan Falk
5
@displayname server http 端口是程序员自定义的配置,而非在 Spring 中预定义的。代码中可以看到:@Value("${server.http.port}") private int httpPort; - user666
据我所知,以前有这个。 - Stefan Falk
@Harish Gokavarapu 和 Adam Millerchip,如何在命令行中执行此操作?使用 https Spring Boot 配置,我尝试了以下命令:-Dserver.port=6010 -Dserver.http.port=6011,但只有 https 端口被更改,http 端口未更改。 - Artanis Zeratul
@displayname 和 user666,我们如何在保持 https 的 server.port 的同时设置 http.server.port? - Artanis Zeratul
1
这对我有用,但我不得不切换端口值。http.port 应该是基本的 8080,而 server.port 应该是您所需的 https 端口,例如 8443。 - AForsberg

9

以下是一个简单的示例,说明如何为undertow启用HTTP / HTTPS端口。

Spring Boot只允许通过配置打开一个端口。第二个端口必须通过编程方式打开。

首先以编程方式打开HTTP端口。

import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;

@Configuration
public class UndertowConfig {

@Value("${server.http.port}")
private int httpPort;

@Value("${server.http.interface}")
private String httpInterface;

@Bean
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> containerCustomizer() {
    return (WebServerFactoryCustomizer) factory -> {
        UndertowServletWebServerFactory undertowFactory = (UndertowServletWebServerFactory) factory;
        undertowFactory.getBuilderCustomizers().add(builder -> {
            builder.addHttpListener(httpPort, httpInterface);
        });
    };
}

HTTPS通过配置实现

HTTPS通过配置实现

Spring可以从可用的属性源读取属性,打开HTTP或HTTPS端口之一。如果您添加适当的配置如下所示,则足以打开HTTPs端口。

#default secured port (Spring will open it automatically)
server.port=8443
#additional HTTP port (will open it in UndertowConfig)
server.http.port=8080
#Open to the world
server.http.interface=0.0.0.0
#These settings tell Spring to open SSL port
server.ssl.keystore=file:${APP_BASE}/conf/server/ssl_selfsigned/server.keystore
server.ssl.key-store-password=xyz
server.ssl.key-password=xyz

手动设置HTTPS

如果您希望打开与HTTP端口相同的SSL端口,则可以通过以下方式完成:

 .addHttpsListener(ssl_port, httpInterface, getSSLContext());

以下是创建 SSL 上下文的方法:

import javax.net.ssl.*;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;

public SSLContext getSSLContext() throws Exception
{
    return createSSLContext(loadKeyStore(serverKeystore,keyStorePassword),
            loadKeyStore(serverTruststore,trustStorePassword));

}


private SSLContext createSSLContext(final KeyStore keyStore,
                                    final KeyStore trustStore) throws Exception {

    KeyManager[] keyManagers;
    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
    keyManagers = keyManagerFactory.getKeyManagers();

    TrustManager[] trustManagers;
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(trustStore);
    trustManagers = trustManagerFactory.getTrustManagers();

    SSLContext sslContext;
    sslContext = SSLContext.getInstance("TLS");
    sslContext.init(keyManagers, trustManagers, null);

    return sslContext;
}


private static KeyStore loadKeyStore(final String storeLoc, final String storePw) throws Exception {
    InputStream stream = Files.newInputStream(Paths.get(storeLoc));
    if(stream == null) {
        throw new IllegalArgumentException("Could not load keystore");
    }
    try(InputStream is = stream) {
        KeyStore loadedKeystore = KeyStore.getInstance("JKS");
        loadedKeystore.load(is, storePw.toCharArray());
        return loadedKeystore;
    }
}

这个解决方案在undertow上完美运行,谢谢。 - norbertas.gaulia
请问您能否在上述实现中添加一些HTTP 2.0的代码? - sam
在我的情况下,@Value注入并不那么容易,因为我的配置更加复杂。我在这个GitHub问题评论中解释了我的情况以及最终对我起作用的方法。 - Alejandro González

5

另一种 Spring Boot 2.x 的解决方案:

private static final int HTTP_PORT = 80;
private static final int HTTPS_PORT = 443;
private static final String HTTP = "http";
private static final String USER_CONSTRAINT = "CONFIDENTIAL";

@Bean
public ServletWebServerFactory servletContainer() {
    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
        @Override
        protected void postProcessContext(Context context) {
            SecurityConstraint securityConstraint = new SecurityConstraint();
            securityConstraint.setUserConstraint(USER_CONSTRAINT);
            SecurityCollection collection = new SecurityCollection();
            collection.addPattern("/*");
            securityConstraint.addCollection(collection);
            context.addConstraint(securityConstraint);
        }
    };
    tomcat.addAdditionalTomcatConnectors(redirectConnector());
    return tomcat;
}

private Connector redirectConnector() {
    Connector connector = new Connector(
            TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
    connector.setScheme(HTTP);
    connector.setPort(HTTP_PORT);
    connector.setSecure(false);
    connector.setRedirectPort(HTTPS_PORT);
    return connector;
}

请在您的属性中设置server.port=443


Context 的导入是什么? - MJBZA
1
@MJBZA 导入上下文的语句为 org.apache.catalina。 - Tomislav Brabec

0

请查看:https://github.com/creactiviti/spring-boot-starter-acme。它使自动生成基于LetsEncrypt的SSL证书变得非常容易。

从README中:

  1. 将该模块作为依赖项添加到您的pom.xml文件中。

  2. 构建您的项目。

  3. 将其部署到目标机器,并将您的域名指向该机器的IP地址。LetsEncrypt通过调用此模块公开的http://your-domain/.well-known/acme-challenge/{token}端点来验证您对该域名的所有权。

  4. 确保您的服务器在$PATH上可用openssl。

  5. 要激活spring-boot-starter-acme并生成证书,请执行以下操作:

    sudo java -Dserver.port=80 -Dacme.enabled=true -Dacme.domain-name=<YOUR_DOMAIN_NAME> -Dacme.accept-terms-of-service=true -jar mysecureapp-0.0.1-SNAPSHOT.jar

  6. 检查控制台以确认证书已成功生成。

  7. 停止应用程序并配置它以使用生成的证书:

    server.port=443 server.ssl.key-store=keystore.p12 server.ssl.key-store-password=password server.ssl.keyStoreType=PKCS12


理想情况下,也是我来这里的原因,是Web应用程序在运行时可以自动更新证书。为了使其正常工作,我必须在HTTP中公开*/.well-known/acme-challenge端点,但这是一个问题,因为我的Web应用程序的其余部分都在HTTPS上运行。 - Vanheden

0

顶尖答案都很棒,可能都能用,但我一直在使用Undertow和JHipster,所以它们对我没有用(而且这是主要的搜索结果)。有关Undertow的正确代码特别在此问题中提到:

@Bean
public UndertowServletWebServerFactory embeddedServletContainerFactory() {
    UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
    factory.addBuilderCustomizers(new UndertowBuilderCustomizer() {
        @Override
        public void customize(Undertow.Builder builder) {
            builder.addHttpListener(8080, "0.0.0.0");
        }
    });
    return factory;
}

尽量避免用已经存在的答案来回答问题。 - Viorel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接