Spring WebFlux: обновление SSL сертификата
Нужно время от времени, при изменении файла сертификата (он обновляется внешней системой), подхватывать этот новый файл и применять без перезапуска приложения. Вроде бы тривиальная задача, но нигде не могу найти рабочий пример. В моем приложении используется spring-cloud-gateway, соответственно стек Spring + WebFlux. При запуске сервера, первоначально он правильно подхватывает сертификат и успешно его применяет, однако после обновления самого файла сертификата, он не видит обновления. Код конфигурации такой:
@Configuration
public class ContainerConfiguration implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
private final ApplicationContext applicationContext;
private final boolean sslEnabled;
private final String sslKeyStoreType;
private final String sslKeyStoreFilepath;
private final String sslKeyStorePassword;
private final String sslKeyAlias;
public ContainerConfiguration(ApplicationContext applicationContext,
@Value("${app.ssl.enabled}") String appSslEnabledStr,
@Value("${server.ssl.key-store-type}") String sslKeyStoreType,
@Value("${server.ssl.key-store}") String sslKeyStoreFilepath,
@Value("${server.ssl.key-store-password}") String sslKeyStorePassword,
@Value("${server.ssl.key-alias}") String sslKeyAlias)
{
this.applicationContext = applicationContext;
this.sslEnabled = "1".equals(appSslEnabledStr);
this.sslKeyStoreType = sslKeyStoreType;
this.sslKeyStoreFilepath = sslKeyStoreFilepath;
this.sslKeyStorePassword = sslKeyStorePassword;
this.sslKeyAlias = sslKeyAlias;
}
@Override
public void customize(NettyReactiveWebServerFactory factory) {
Ssl ssl = new Ssl();
ssl.setEnabled(sslEnabled);
if (sslEnabled) {
ssl.setKeyStoreType(sslKeyStoreType);
ssl.setKeyStore(sslKeyStoreFilepath);
ssl.setKeyStorePassword(sslKeyStorePassword);
ssl.setKeyAlias(sslKeyAlias);
WatchService watchService;
Path certPath = Paths.get(sslKeyStoreFilepath);
try {
watchService = FileSystems.getDefault().newWatchService();
certPath.getParent().register(watchService, ENTRY_MODIFY);
} catch (IOException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
WatchKey key = watchService.take();// Ждем событие обновления файла
try {
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == ENTRY_MODIFY && event.context().equals(certPath.getFileName())) {
log.warn("SSL certificate is changed. Reloading the context...");
reloadSSLConfig(factory);
}
}
} finally {
key.reset(); // готовимся дать следующее событие
}
} catch (Exception e) {
log.error(e.getMessage());
}
}
}).start();
}
factory.setPort(sslEnabled ? 8443 : 8080);
factory.setSsl(ssl);
}
private void reloadSSLConfig(@NonNull NettyReactiveWebServerFactory factory) {
Ssl ssl = factory.getSsl();
// Ssl ssl = new Ssl();
// ssl.setEnabled(sslEnabled);
// ssl.setKeyStoreType(sslKeyStoreType);
ssl.setKeyStore(sslKeyStoreFilepath);
ssl.setKeyStorePassword(sslKeyStorePassword);
// ssl.setKeyAlias(sslKeyAlias);
// factory.setPort(sslEnabled ? 8443 : 8080);
factory.setSsl(ssl); // important: update ssl
// Перезагрузка контекста Spring Boot
// ((AbstractApplicationContext) applicationContext).stop();
// ((AbstractApplicationContext) applicationContext).start();
}
}
Т.е. там после WatchService watchService происходит ожидание обновления файла сертификата, после его обновления применяется код из reloadSSLConfig - это работает.
А вот в самом методе reloadSSLConfig я прописываю новый factory.setSsl(ssl); и, ожидаемо, сервер не перезагружает файл сертификата, а использует старую конфигурацию. И даже после перезагрузки applicationContext (закомменчено внизу), конфигуация SSL по-прежнему используется старая.
Подскажите, как сказать серверу чтобы он обновил конфигурацию?
Ответы (1 шт):
Пошел наиболее простым путем и решил просто перезапускать приложение после обновления сертификата через actuator. Для этого убедился, что в application.properties включен restart endpoint:
management.endpoint.restart.access=read_only
В своем ContainerConfiguration.java:
autowire RestartEndpoint
метод reloadSSLConfig получился таким:
private void reloadSSLConfig() { // Перезагрузка приложения
restartEndpoint.restart(); }
PS: еще нашел статью о Hot reload, может кому-нибудь поможет: SSL hot reload in Spring Boot 3.2.0