У меня есть 2 службы A и B, которые должны общаться друг с другом через HTTPS. Я включил TLS, используя свойства server.ssl.* загрузки Spring для обоих приложений. Я использую WebClient для связи, когда служба A будет вызывать B с помощью веб-клиента, а B отправит ответ обратно A. Насколько я понимаю, для связи через TLS веб-клиенту потребуются данные хранилища доверенных сертификатов, которые будут иметь сертификаты службы, которая вызывается, т.е. Служба B.
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;
import org.springframework.boot.web.server.Ssl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.extern.slf4j.Slf4j;
import reactor.netty.http.client.HttpClient;
@Slf4j
@Configuration
public class WebClientConfig {
private final Ssl ssl;
public WebClientConfig(final Ssl ssl) {
this.ssl = ssl;
}
@Bean
public WebClient createWebClient() throws Exception {
if (ssl.isEnabled()) {
return buildSslEnabledWebClient();
}
return WebClient.builder().build();
}
private WebClient buildSslEnabledWebClient() throws Exception {
final String trustStorePath = ssl.getTrustStore();
final String trustStorePassword = ssl.getTrustStorePassword();
final KeyStore trustStore = createKeyStore(trustStorePath, trustStorePassword);
try {
final TrustManagerFactory trustManager = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManager.init(trustStore);
final SslContext sslContext = SslContextBuilder.forClient().trustManager(trustManager)
.build();
final HttpClient httpClient = HttpClient.create().secure(ssl -> {
ssl.sslContext(sslContext);
});
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build();
} catch (NoSuchAlgorithmException | KeyStoreException | SSLException e) {
log.error("Could not initialize Webclient with the trustore data", e);
throw e;
}
}
private static KeyStore createKeyStore(final String keyStoreLocation, final String keyStorePassword) {
try (FileInputStream fis = new FileInputStream(keyStoreLocation)) {
final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(fis, keyStorePassword.toCharArray());
return ks;
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
throw new IllegalArgumentException(e);
}
}
}
Я создал самоподписанные сертификаты с помощью keytool, и это, похоже, работает. Я мог бы позвонить из А в Б и ответить обратно.
Однако я почему-то чувствую, что настройка не завершена, и поэтому мои вопросы:
- Я что-то упускаю?
- Это правильный способ сделать это?
- Будет ли это работать в производстве с реальными сертификатами?
ОБНОВЛЕНИЕ: исправлен код, понял ошибку после прочтения ответа ниже. Я на самом деле использую обновленный код.