Допустим, у нас есть приложение на Spring, оно отправляет rest-запросы в другой сервис по https. Нужно настроить подключение нашего клиента - spring web client к серверу с использованием ssl.
Не нашел явно подходящего мне решения в интернете, поэтому хочу поделиться, как это получилось у меня. В решении мне помогли stackoverflow и пример конфигурирования SSL (ссылки в конце).
Начинаем
Конфигурация
У нас есть файл со свойствами - application.properties или application.yml. В нем мы укажем путь к файлу с сертификатами пользователя и ключами, путь к файлу с доверенными сертификатами (обычно это один файл) и пароли.
ssl:
trust-store-location: classpath:key.jks
trust-store-password: password
trust-store-type: jks
key-store-location: classpath:key.jks
key-store-password: password
key-store-type: jks
key-password: password
Создадим класс, который будет соответствовать этой конфигурации.
@Configuration
@ConfigurationProperties("ssl")
@Data
public class SslProperties {
Resource trustStoreLocation;
String trustStorePassword;
String trustStoreType;
Resource keyStoreLocation;
String keyStorePassword;
String keyStoreType;
String keyPassword;
}
Создадим бин, который будет выполнять “пред-настройку” нашего клиента:
@Configuration
@AllArgsConstructor
public class HttpConfig {
private SslProperties props;
@Bean
public WebClientCustomizer configureWebclient() {
return (WebClient.Builder builder) -> {
try {
SslContext sslContext = createSslContext();
HttpClient httpClient = HttpClient.create()
.secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
builder.clientConnector(connector);
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException e) {
throw new RuntimeException(e);
}
};
}
private SslContext createSslContext() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
// инициализируем keyManagerFactory для keyStore
KeyStore keyStore = KeyStore.getInstance(props.getKeyStoreType());
keyStore.load(props.getKeyStoreLocation().getInputStream(), props.getKeyStorePassword().toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, props.getKeyPassword().toCharArray());
// инициализируем keyManagerFactory для trustStore
KeyStore trustStore = KeyStore.getInstance(props.getTrustStoreType());
trustStore.load(props.getTrustStoreLocation().getInputStream(), props.getTrustStorePassword().toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
return SslContextBuilder.forClient()
.trustManager(trustManagerFactory)
.keyManager(keyManagerFactory)
.build();
}
}
Использование
Далее подключим этот бин в клиенте:
@Service
@RequiredArgsConstructor
public class Client {
private final WebClient.Builder builder;
public void sendRq() {
builder.build()
.post()
// далее логика по отправке запроса и обработке ответа
}
}
Тестирование
Тестировал эту штуку с mockito. Сконфигурировал примерно так:
public class ClientTest {
private Client client;
private WebClient.Builder builder;
private WebClient webClientMock;
@BeforeEach
public void setUp() {
webClientMock = mock(WebClient.class);
builder = mock(WebClient.Builder.class);
client = spy(new Client(builder));
}
@Test
public void testFeatureShouldWork() {
when(builder.build()).thenReturn(webClientMock);
}
}
Заключение
Кажется, что получилась довольно гибкая и удобная конфигурация. Если у вас есть опыт или интересные ссылки для конфигурирования SSL в spring web client, поделитесь, пожалуйста, в комментариях.
Ссылки, которые помогли мне собрать такой вариант:
ultrinfaern
Правильный ответ - никак, так как WebClient это обвертка над HttpClient. И конфигурировать нужно то клиент, который вы будете использовать (встроенный, Apache, OkHttp, ...)