Spring boot have some properties to config web port and SSL settings, but once a SSL certificate is set the http port turns into https port.
So, how can I keep both
The currently accepted answer works perfectly but needs some adaption if you want it to work with Spring Boot 2.0.0 and onwards:
@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;
}
}
or the kotlin version:
@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
}
}
Take a look at: https://github.com/creactiviti/spring-boot-starter-acme. It makes it very easy to auto-generate a LetsEncrypt based SSL certificate.
From the README:
Add the module to your pom.xml file as a dependency.
Build your project.
Deploy it to a target machine and point your domain name to the IP address of that machine. LetsEncrypt validates your ownership of the domain by making a callback to the http://your-domain/.well-known/acme-challenge/{token} endpoint exposed by this module.
Make sure that your server has openssl available on its $PATH.
To activate spring-boot-starter-acme and generate a certificate execute:
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
Check your console for a confirmation that the certificate was successfully generated.
Stop your application and configure it to make use of the generated certificate:
server.port=443
server.ssl.key-store=keystore.p12
server.ssl.key-store-password=password
server.ssl.keyStoreType=PKCS12
The top answers are all great and probably work but I've been using Undertow with JHipster so they didn't work for me (and this was the main search result). The right code for Undertow is mentioned in this issue specifically:
@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;
}
Spring Boot configuration using properties, allows configuring only one connector. What you need is multiple connectors and for this you have to write a Configuration class. Follow instructions in
https://docs.spring.io/spring-boot/docs/1.2.3.RELEASE/reference/html/howto-embedded-servlet-containers.html
You can find a working example of configuring https through properties and then http though EmbeddedServletContainerCustomizer below
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);
}
}
};
}
}
Spring Boot only lets to open one port by configuration. Second port has to be opened programmatically.
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);
});
};
}
}
Spring can open one either HTTP or HTTPS port reading properties from an available property source. If you add appropriate configuration as shown bellow it would be good enough to have HTTPs port open.
#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
You can open another SSL port the same way as you opened HTTP port if you want by doing this
.addHttpsListener(ssl_port, httpInterface, getSSLContext());
This is how you can create SSL context
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;
}
}
Another Spring boot 2.x solution:
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;
}
And set in your properties server.port=443