По какой-то причине сервер не принял сертификат клиента, когда контекст ssl был построен следующим образом:
sslContext = SslContextBuilder.forClient()
.keyManager((PrivateKey) keyStore.getKey(keyAlias, keyStorePass.toCharArray()))
.trustManager((X509Certificate[]) certificateCollcetion.toArray(new X509Certificate[certificateCollcetion.size()]))
.build();
Чтобы это исправить, мне нужно было инициализировать KeyManagerFactory:
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, keyStorePass.toCharArray());
Затем я инициализировал контекст ssl с фабрикой:
SslContext sslContext = SslContextBuilder.forClient()
.keyManager(keyManagerFactory)
.trustManager((X509Certificate[]) certificateCollection.toArray(new X509Certificate[certificateCollection.size()]))
.build();
После этого сервер принял сертификат, и я смог подключиться.
Итак, я использовал это более чистое решение, которое использует фабрики как для хранилища ключей, так и для хранилища доверия:
@Value("${server.ssl.trust-store}")
String trustStorePath;
@Value("${server.ssl.trust-store-password}")
String trustStorePass;
@Value("${server.ssl.key-store}")
String keyStorePath;
@Value("${server.ssl.key-store-password}")
String keyStorePass;
@Bean
public WebClient create2WayTLSWebClient() {
ClientHttpConnector connector = new ReactorClientHttpConnector(
options -> {
options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
options.sslContext(get2WaySSLContext());
}
);
return WebClient.builder()
.clientConnector(connector)
.build();
}
private SslContext get2WaySSLContext() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(ResourceUtils.getFile(keyStorePath)), keyStorePass.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, keyStorePass.toCharArray());
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(new FileInputStream(ResourceUtils.getFile(trustStorePath)), trustStorePass.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(trustStore);
return SslContextBuilder.forClient()
.keyManager(keyManagerFactory)
.trustManager(trustManagerFactory)
.build();
} catch (Exception e) {
logger.error("Error creating 2-Way TLS WebClient. Check key-store and trust-store.");
e.printStackTrace();
}
return null;
}
Просто обратите внимание: если вы используете Spring 5.1 или новее, эта конкретная реализация не будет работать, поскольку вы больше не сможете передавать HttpClientOptions в ReactorClientHttpConnector. Используйте эту ссылку в качестве руководства для этой конфигурации. Однако основная часть кода в этом ответе все еще должна быть применима к такой конфигурации.