With any change to the SSL certificates (in its keystore), we need to restart the spring boot application. I want to update my key store entry periodically (may be every year), but want to avoid restarting the JVM. What would it take to achieve it. I wonder if writing custom KeyManager is an acceptable practice?
One of Spring Boot’s key features is creating a Java web application as a standalone, executable JAR file with embedded server like Tomcat. However, this feature comes with a drawback in development time: every changes you made to your project requires complete application restart to take effect.
Spring Boot has a Developer tools (devtools) module helps to improve the productivity of developers. Let’s create Spring Boot project in Eclipse IDE. The name of the project is spring-boot-reload-changes-without-server-restart. Now running the above main class will deploy your application and server will be started on port 8080.
By default, the jar files of Spring Boot DevTools are not included in your project’s package (JAR/WAR). If you no longer want to use automatic restart, just remove the spring-boot-devtools dependency in the pom.xml file.
By default, any changes (create, update or delete) you made to any files in the project’s classpath (typically files under src/main directory) will trigger Spring DevTools to restart the application to update the changes. Actually, Spring Boot DevTools monitors changes for all entries under the project’s classpath.
Unfortunately, this is not possible.
You have several solutions here.
You can restart Tomcat
connector i.e. restart 8843
is possible after you change your jssecacert
file.
But I think that it is still a hack.
Nginx
, Apache
This is a way to go. Your application should be behind some reverse proxy (e.g. nginx
). This will give you an additional flexibility and reduce load on your app. Nginx
will handle https
and translate it to plain http
. Anyway, you'll have to restart nginx
, but nginx
restart is so fast that it will be no downtime. Moreover, you could configure script to do this for you.
On Tomcat one can use local JMX to reload SSL context:
private static final String JMX_THREAD_POOL_NAME = "*:type=ThreadPool,name=*";
private static final String JMX_OPERATION_RELOAD_SSL_HOST_CONFIGS_NAME = "reloadSslHostConfigs";
private void reloadSSLConfigsOnConnectors() {
try {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName(JMX_THREAD_POOL_NAME);
Set<ObjectInstance> allTP = server.queryMBeans(objectName, null);
logger.info("MBeans found: {}", allTP.size());
allTP.forEach(tp -> reloadSSLConfigOnThreadPoolJMX(server, tp));
} catch (Exception ex) {
logger.error("", ex);
}
}
private void reloadSSLConfigOnThreadPoolJMX(MBeanServer server, ObjectInstance tp) {
try {
logger.info("Invoking operation SSL reload on {}", tp.getObjectName());
server.invoke(tp.getObjectName(), JMX_OPERATION_RELOAD_SSL_HOST_CONFIGS_NAME, new Object[]{}, new String[]{});
logger.trace("Successfully invoked");
} catch (Exception ex) {
logger.error("Invoking SSL reload", ex);
}
}
I'm reloading all ThreadPool SSL context, but you really only need one: Tomcat:type=ThreadPool,name=https-jsse-nio-8443
. I'm just afraid the name will change, so I cover all possibilities just in case.
I have solved this problem in my Spring Boot application by getting a
TomcatServletWebServerFactory
in a bean and add my own connector customizer to it.
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
// --- CUSTOMIZE SSL PORT IN ORDER TO BE ABLE TO RELOAD THE SSL HOST CONFIG
tomcat.addConnectorCustomizers(new DefaultSSLConnectorCustomizer());
return tomcat;
}
My customizer extracts the protocol for https for later use
public class DefaultSSLConnectorCustomizer implements TomcatConnectorCustomizer {
private Http11NioProtocol protocol;
@Override
public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
if ( connector.getSecure()) {
//--- REMEMBER PROTOCOL WHICH WE NEED LATER IN ORDER TO RELOAD SSL CONFIG
this.protocol = protocol;
}
}
protected Http11NioProtocol getProtocol() {
return protocol;
}
}
When I have updated the keystore with the new private key I conduct a SSL host config reload. This code goes here
@Component
public class TomcatUtil {
public static final String DEFAULT_SSL_HOSTNAME_CONFIG_NAME = "_default_";
private final Logger logger = LoggerFactory.getLogger(getClass());
private ServletWebServerFactory servletWebServerFactory;
public TomcatUtil(ServletWebServerFactory servletWebServerFactory) {
this.servletWebServerFactory = servletWebServerFactory;
}
public void reloadSSLHostConfig() {
TomcatServletWebServerFactory tomcatFactoty = (TomcatServletWebServerFactory) servletWebServerFactory;
Collection<TomcatConnectorCustomizer> customizers = tomcatFactoty.getTomcatConnectorCustomizers();
for (TomcatConnectorCustomizer tomcatConnectorCustomizer : customizers) {
if (tomcatConnectorCustomizer instanceof DefaultSSLConnectorCustomizer) {
DefaultSSLConnectorCustomizer customizer = (DefaultSSLConnectorCustomizer) tomcatConnectorCustomizer;
Http11NioProtocol protocol = customizer.getProtocol();
try {
protocol.reloadSslHostConfig(DEFAULT_SSL_HOSTNAME_CONFIG_NAME);
logger.info("Reloaded SSL host configuration");
} catch (IllegalArgumentException e) {
logger.warn("Cannot reload SSL host configuration", e);
}
}
}
}
}
And finally
...
renewServerCertificate();
tomcatUtil.reloadSSLHostConfig();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With