diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java
new file mode 100644
index 00000000..b15f032f
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.client;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import io.netty.channel.ChannelOption;
+import io.netty.handler.ssl.SslContextBuilder;
+import reactor.netty.http.client.HttpClient;
+
+import org.springframework.http.client.reactive.ClientHttpConnector;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.vault.support.SslConfiguration;
+
+import static org.springframework.vault.client.ClientHttpRequestFactoryFactory.createKeyManagerFactory;
+import static org.springframework.vault.client.ClientHttpRequestFactoryFactory.createTrustManagerFactory;
+import static org.springframework.vault.client.ClientHttpRequestFactoryFactory.hasSslConfiguration;
+
+/**
+ * Factory for {@link ClientHttpConnector} that supports
+ * {@link ReactorClientHttpConnector}.
+ *
+ * @author Mark Paluch
+ * @since 2.2
+ */
+public class ClientHttpConnectorFactory {
+
+ /**
+ * Create a {@link ClientHttpConnector} for the given {@link ClientOptions} and
+ * {@link SslConfiguration}.
+ *
+ * @param options must not be {@literal null}
+ * @param sslConfiguration must not be {@literal null}
+ * @return a new {@link ClientHttpConnector}.
+ */
+ public static ClientHttpConnector create(ClientOptions options,
+ SslConfiguration sslConfiguration) {
+
+ HttpClient client = HttpClient.create();
+
+ if (hasSslConfiguration(sslConfiguration)) {
+
+ SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
+ configureSsl(sslConfiguration, sslContextBuilder);
+
+ client = client.secure(builder -> {
+ builder.sslContext(sslContextBuilder);
+ });
+ }
+
+ client = client.tcpConfiguration(it -> it.option(
+ ChannelOption.CONNECT_TIMEOUT_MILLIS,
+ Math.toIntExact(options.getConnectionTimeout().toMillis())));
+
+ return new ReactorClientHttpConnector(client);
+ }
+
+ private static void configureSsl(SslConfiguration sslConfiguration,
+ SslContextBuilder sslContextBuilder) {
+
+ try {
+
+ if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
+ sslContextBuilder.trustManager(createTrustManagerFactory(sslConfiguration
+ .getTrustStoreConfiguration()));
+ }
+
+ if (sslConfiguration.getKeyStoreConfiguration().isPresent()) {
+ sslContextBuilder.keyManager(createKeyManagerFactory(
+ sslConfiguration.getKeyStoreConfiguration(),
+ sslConfiguration.getKeyConfiguration()));
+ }
+ }
+ catch (GeneralSecurityException | IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java
new file mode 100644
index 00000000..2136cc58
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2016-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ProxySelector;
+import java.net.Socket;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.KeyManagerFactorySpi;
+import javax.net.ssl.ManagerFactoryParameters;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import okhttp3.OkHttpClient.Builder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.client.LaxRedirectStrategy;
+import org.apache.http.impl.conn.DefaultSchemePortResolver;
+import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
+
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.client.Netty4ClientHttpRequestFactory;
+import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.vault.support.SslConfiguration;
+import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
+
+import static org.springframework.vault.support.SslConfiguration.KeyConfiguration;
+
+/**
+ * Factory for {@link ClientHttpRequestFactory} that supports Apache HTTP Components,
+ * OkHttp, Netty and the JDK HTTP client (in that order). This factory configures a
+ * {@link ClientHttpRequestFactory} depending on the available dependencies.
+ *
+ * @author Mark Paluch
+ * @since 2.2
+ */
+public class ClientHttpRequestFactoryFactory {
+
+ private static final Log logger = LogFactory
+ .getLog(ClientHttpRequestFactoryFactory.class);
+
+ private static final boolean HTTP_COMPONENTS_PRESENT = isPresent("org.apache.http.client.HttpClient");
+
+ private static final boolean OKHTTP3_PRESENT = isPresent("okhttp3.OkHttpClient");
+
+ private static final boolean NETTY_PRESENT = isPresent(
+ "io.netty.channel.nio.NioEventLoopGroup", "io.netty.handler.ssl.SslContext",
+ "io.netty.handler.codec.http.HttpClientCodec");
+
+ /**
+ * Checks for presence of all {@code classNames} using this class' classloader.
+ *
+ * @param classNames
+ * @return {@literal true} if all classes are present; {@literal false} if at least
+ * one class cannot be found.
+ */
+ private static boolean isPresent(String... classNames) {
+
+ for (String className : classNames) {
+ if (!ClassUtils.isPresent(className,
+ ClientHttpRequestFactoryFactory.class.getClassLoader())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Create a {@link ClientHttpRequestFactory} for the given {@link ClientOptions} and
+ * {@link SslConfiguration}.
+ *
+ * @param options must not be {@literal null}
+ * @param sslConfiguration must not be {@literal null}
+ * @return a new {@link ClientHttpRequestFactory}. Lifecycle beans must be initialized
+ * after obtaining.
+ */
+ public static ClientHttpRequestFactory create(ClientOptions options,
+ SslConfiguration sslConfiguration) {
+
+ Assert.notNull(options, "ClientOptions must not be null");
+ Assert.notNull(sslConfiguration, "SslConfiguration must not be null");
+
+ try {
+
+ if (HTTP_COMPONENTS_PRESENT) {
+ return HttpComponents.usingHttpComponents(options, sslConfiguration);
+ }
+
+ if (OKHTTP3_PRESENT) {
+ return OkHttp3.usingOkHttp3(options, sslConfiguration);
+ }
+
+ if (NETTY_PRESENT) {
+ return Netty.usingNetty(options, sslConfiguration);
+ }
+ }
+ catch (GeneralSecurityException e) {
+ throw new IllegalStateException(e);
+ }
+ catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+ if (hasSslConfiguration(sslConfiguration)) {
+ logger.warn("VaultProperties has SSL configured but the SSL configuration "
+ + "must be applied outside the Vault Client to use the JDK HTTP client");
+ }
+
+ return new SimpleClientHttpRequestFactory();
+ }
+
+ static SSLContext getSSLContext(SslConfiguration sslConfiguration,
+ TrustManager[] trustManagers) throws GeneralSecurityException, IOException {
+
+ KeyConfiguration keyConfiguration = sslConfiguration.getKeyConfiguration();
+ KeyManager[] keyManagers = sslConfiguration.getKeyStoreConfiguration()
+ .isPresent() ? createKeyManagerFactory(
+ sslConfiguration.getKeyStoreConfiguration(), keyConfiguration)
+ .getKeyManagers() : null;
+
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(keyManagers, trustManagers, null);
+
+ return sslContext;
+ }
+
+ private static TrustManager[] getTrustManagers(SslConfiguration sslConfiguration)
+ throws GeneralSecurityException, IOException {
+
+ return sslConfiguration.getTrustStoreConfiguration().isPresent() ? createTrustManagerFactory(
+ sslConfiguration.getTrustStoreConfiguration()).getTrustManagers()
+ : null;
+ }
+
+ static KeyManagerFactory createKeyManagerFactory(
+ KeyStoreConfiguration keyStoreConfiguration, KeyConfiguration keyConfiguration)
+ throws GeneralSecurityException, IOException {
+
+ KeyStore keyStore = KeyStore.getInstance(StringUtils
+ .hasText(keyStoreConfiguration.getStoreType()) ? keyStoreConfiguration
+ .getStoreType() : KeyStore.getDefaultType());
+
+ loadKeyStore(keyStoreConfiguration, keyStore);
+
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory
+ .getInstance(KeyManagerFactory.getDefaultAlgorithm());
+
+ char[] keyPasswordToUse = keyConfiguration.getKeyPassword();
+
+ if (keyPasswordToUse == null) {
+ keyPasswordToUse = keyStoreConfiguration.getStorePassword() == null ? new char[0]
+ : keyStoreConfiguration.getStorePassword();
+ }
+
+ keyManagerFactory.init(keyStore, keyPasswordToUse);
+
+ if (StringUtils.hasText(keyConfiguration.getKeyAlias())) {
+ return new KeySelectingKeyManagerFactory(keyManagerFactory, keyConfiguration);
+ }
+
+ return keyManagerFactory;
+ }
+
+ static TrustManagerFactory createTrustManagerFactory(
+ KeyStoreConfiguration keyStoreConfiguration) throws GeneralSecurityException,
+ IOException {
+
+ KeyStore trustStore = KeyStore.getInstance(StringUtils
+ .hasText(keyStoreConfiguration.getStoreType()) ? keyStoreConfiguration
+ .getStoreType() : KeyStore.getDefaultType());
+
+ loadKeyStore(keyStoreConfiguration, trustStore);
+
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory
+ .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(trustStore);
+
+ return trustManagerFactory;
+ }
+
+ private static void loadKeyStore(KeyStoreConfiguration keyStoreConfiguration,
+ KeyStore keyStore) throws IOException, NoSuchAlgorithmException,
+ CertificateException {
+
+ InputStream inputStream = null;
+ try {
+ inputStream = keyStoreConfiguration.getResource().getInputStream();
+ keyStore.load(inputStream, keyStoreConfiguration.getStorePassword());
+ }
+ finally {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ }
+ }
+
+ static boolean hasSslConfiguration(SslConfiguration sslConfiguration) {
+ return sslConfiguration.getTrustStoreConfiguration().isPresent()
+ || sslConfiguration.getKeyStoreConfiguration().isPresent();
+ }
+
+ /**
+ * {@link ClientHttpRequestFactory} for Apache Http Components.
+ *
+ * @author Mark Paluch
+ */
+ static class HttpComponents {
+
+ static ClientHttpRequestFactory usingHttpComponents(ClientOptions options,
+ SslConfiguration sslConfiguration) throws GeneralSecurityException,
+ IOException {
+
+ HttpClientBuilder httpClientBuilder = HttpClients.custom();
+
+ httpClientBuilder.setRoutePlanner(new SystemDefaultRoutePlanner(
+ DefaultSchemePortResolver.INSTANCE, ProxySelector.getDefault()));
+
+ if (hasSslConfiguration(sslConfiguration)) {
+
+ SSLContext sslContext = getSSLContext(sslConfiguration,
+ getTrustManagers(sslConfiguration));
+ SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
+ sslContext);
+ httpClientBuilder.setSSLSocketFactory(sslSocketFactory);
+ httpClientBuilder.setSSLContext(sslContext);
+ }
+
+ RequestConfig requestConfig = RequestConfig
+ .custom()
+ //
+ .setConnectTimeout(
+ Math.toIntExact(options.getConnectionTimeout().toMillis())) //
+ .setSocketTimeout(
+ Math.toIntExact(options.getReadTimeout().toMillis())) //
+ .setAuthenticationEnabled(true) //
+ .build();
+
+ httpClientBuilder.setDefaultRequestConfig(requestConfig);
+
+ // Support redirects
+ httpClientBuilder.setRedirectStrategy(new LaxRedirectStrategy());
+
+ return new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
+ }
+ }
+
+ /**
+ * {@link ClientHttpRequestFactory} for the {@link okhttp3.OkHttpClient}.
+ *
+ * @author Mark Paluch
+ */
+ static class OkHttp3 {
+
+ static ClientHttpRequestFactory usingOkHttp3(ClientOptions options,
+ SslConfiguration sslConfiguration) throws GeneralSecurityException,
+ IOException {
+
+ Builder builder = new Builder();
+
+ if (hasSslConfiguration(sslConfiguration)) {
+
+ TrustManager[] trustManagers = getTrustManagers(sslConfiguration);
+
+ if (trustManagers.length != 1
+ || !(trustManagers[0] instanceof X509TrustManager)) {
+ throw new IllegalStateException("Unexpected default trust managers:"
+ + Arrays.toString(trustManagers));
+ }
+
+ X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
+ SSLContext sslContext = getSSLContext(sslConfiguration, trustManagers);
+
+ builder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
+ }
+
+ builder.connectTimeout(options.getConnectionTimeout().toMillis(),
+ TimeUnit.MILLISECONDS).readTimeout(
+ options.getReadTimeout().toMillis(), TimeUnit.MILLISECONDS);
+
+ return new OkHttp3ClientHttpRequestFactory(builder.build());
+ }
+ }
+
+ /**
+ * {@link ClientHttpRequestFactory} for Netty.
+ *
+ * @author Mark Paluch
+ */
+ static class Netty {
+
+ static ClientHttpRequestFactory usingNetty(ClientOptions options,
+ SslConfiguration sslConfiguration) throws GeneralSecurityException,
+ IOException {
+
+ final Netty4ClientHttpRequestFactory requestFactory = new Netty4ClientHttpRequestFactory();
+
+ if (hasSslConfiguration(sslConfiguration)) {
+
+ SslContextBuilder sslContextBuilder = SslContextBuilder //
+ .forClient();
+
+ if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
+ sslContextBuilder
+ .trustManager(createTrustManagerFactory(sslConfiguration
+ .getTrustStoreConfiguration()));
+ }
+
+ if (sslConfiguration.getKeyStoreConfiguration().isPresent()) {
+ sslContextBuilder.keyManager(createKeyManagerFactory(
+ sslConfiguration.getKeyStoreConfiguration(),
+ sslConfiguration.getKeyConfiguration()));
+ }
+
+ requestFactory.setSslContext(sslContextBuilder.sslProvider(
+ SslProvider.JDK).build());
+ }
+
+ requestFactory.setConnectTimeout(Math.toIntExact(options
+ .getConnectionTimeout().toMillis()));
+ requestFactory.setReadTimeout(Math.toIntExact(options.getReadTimeout()
+ .toMillis()));
+
+ return requestFactory;
+ }
+ }
+
+ static class KeySelectingKeyManagerFactory extends KeyManagerFactory {
+
+ KeySelectingKeyManagerFactory(KeyManagerFactory factory,
+ KeyConfiguration keyConfiguration) {
+ super(new KeyManagerFactorySpi() {
+ @Override
+ protected void engineInit(KeyStore keyStore, char[] chars)
+ throws KeyStoreException, NoSuchAlgorithmException,
+ UnrecoverableKeyException {
+ factory.init(keyStore, chars);
+ }
+
+ @Override
+ protected void engineInit(
+ ManagerFactoryParameters managerFactoryParameters)
+ throws InvalidAlgorithmParameterException {
+ factory.init(managerFactoryParameters);
+ }
+
+ @Override
+ protected KeyManager[] engineGetKeyManagers() {
+
+ KeyManager[] keyManagers = factory.getKeyManagers();
+
+ if (keyManagers.length == 1
+ && keyManagers[0] instanceof X509ExtendedKeyManager) {
+
+ return new KeyManager[] { new KeySelectingX509KeyManager(
+ (X509ExtendedKeyManager) keyManagers[0], keyConfiguration) };
+ }
+
+ return keyManagers;
+ }
+ }, factory.getProvider(), factory.getAlgorithm());
+ }
+ }
+
+ private static class KeySelectingX509KeyManager extends X509ExtendedKeyManager {
+
+ private final X509ExtendedKeyManager delegate;
+ private final KeyConfiguration keyConfiguration;
+
+ KeySelectingX509KeyManager(X509ExtendedKeyManager delegate,
+ KeyConfiguration keyConfiguration) {
+ this.delegate = delegate;
+ this.keyConfiguration = keyConfiguration;
+ }
+
+ @Override
+ public String[] getClientAliases(String keyType, Principal[] issuers) {
+ return delegate.getClientAliases(keyType, issuers);
+ }
+
+ @Override
+ public String chooseClientAlias(String[] keyType, Principal[] issuers,
+ Socket socket) {
+ return keyConfiguration.getKeyAlias();
+ }
+
+ public String chooseEngineClientAlias(String[] keyType, Principal[] issuers,
+ SSLEngine engine) {
+ return keyConfiguration.getKeyAlias();
+ }
+
+ @Override
+ public String[] getServerAliases(String keyType, Principal[] issuers) {
+ return delegate.getServerAliases(keyType, issuers);
+ }
+
+ @Override
+ public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
+ return delegate.chooseServerAlias(keyType, issuers, socket);
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain(String alias) {
+ return delegate.getCertificateChain(alias);
+ }
+
+ @Override
+ public PrivateKey getPrivateKey(String alias) {
+ return delegate.getPrivateKey(alias);
+ }
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java
index 3ddbf67f..4f28fafd 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ReactiveVaultClients.java
@@ -73,6 +73,26 @@ public class ReactiveVaultClients {
Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null");
Assert.notNull(connector, "ClientHttpConnector must not be null");
+ return createWebClientBuilder(endpointProvider, connector).build();
+ }
+
+ /**
+ * Create a {@link WebClient.Builder} configured with {@link VaultEndpoint} and
+ * {@link ClientHttpConnector}. The client accepts relative URIs without a leading
+ * slash that are expanded to use {@link VaultEndpoint}.
+ *
+ * Requires Jackson 2 for Object-to-JSON mapping.
+ *
+ * @param endpointProvider must not be {@literal null}.
+ * @param connector must not be {@literal null}.
+ * @return the prepared {@link WebClient.Builder}.
+ */
+ static WebClient.Builder createWebClientBuilder(
+ VaultEndpointProvider endpointProvider, ClientHttpConnector connector) {
+
+ Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null");
+ Assert.notNull(connector, "ClientHttpConnector must not be null");
+
UriBuilderFactory uriBuilderFactory = VaultClients
.createUriBuilderFactory(endpointProvider);
@@ -83,7 +103,7 @@ public class ReactiveVaultClients {
cc.decoder(new ByteArrayDecoder());
cc.decoder(new Jackson2JsonDecoder());
- cc.decoder(StringDecoder.allMimeTypes(false));
+ cc.decoder(StringDecoder.allMimeTypes());
cc.encoder(new ByteArrayEncoder());
cc.encoder(new Jackson2JsonEncoder());
@@ -91,7 +111,7 @@ public class ReactiveVaultClients {
}).build();
return WebClient.builder().uriBuilderFactory(uriBuilderFactory)
- .exchangeStrategies(strategies).clientConnector(connector).build();
+ .exchangeStrategies(strategies).clientConnector(connector);
}
/**
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateBuilder.java b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateBuilder.java
new file mode 100644
index 00000000..fedf8f37
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateBuilder.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.vault.support.SslConfiguration;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * Builder that can be used to configure and create a {@link RestTemplate}. Provides
+ * convenience methods to configure {@link #requestFactory(ClientHttpRequestFactory)
+ * ClientHttpRequestFactory}, {@link #errorHandler(ResponseErrorHandler) error handlers}
+ * and {@link #defaultHeader(String, String) default headers}.
+ *
+ * By default the built {@link RestTemplate} will attempt to use the most suitable
+ * {@link ClientHttpRequestFactory} using {@link ClientHttpRequestFactoryFactory#create}.
+ *
+ * @author Mark Paluch
+ * @since 2.2
+ * @see ClientHttpRequestFactoryFactory
+ * @see RestTemplateCustomizer
+ */
+public class RestTemplateBuilder {
+
+ private @Nullable VaultEndpointProvider endpointProvider;
+
+ private Supplier requestFactory = () -> ClientHttpRequestFactoryFactory
+ .create(new ClientOptions(), SslConfiguration.unconfigured());
+
+ private @Nullable ResponseErrorHandler errorHandler;
+
+ private final Map defaultHeaders = new LinkedHashMap<>();
+
+ private final List customizers = new ArrayList<>();
+
+ private final Set> requestCustomizers = new LinkedHashSet<>();
+
+ private RestTemplateBuilder() {
+ }
+
+ /**
+ * Create a new {@link RestTemplateBuilder}.
+ *
+ * @return a new {@link RestTemplateBuilder}.
+ */
+ public static RestTemplateBuilder builder() {
+ return new RestTemplateBuilder();
+ }
+
+ /**
+ * Set the {@link VaultEndpoint} that should with the {@link RestTemplate}.
+ *
+ * @param endpoint the {@link VaultEndpoint} provider.
+ * @return {@code this} {@link RestTemplateBuilder}.
+ */
+ public RestTemplateBuilder endpoint(VaultEndpoint endpoint) {
+ return endpointProvider(SimpleVaultEndpointProvider.of(endpoint));
+ }
+
+ /**
+ * Set the {@link VaultEndpointProvider} that should with the {@link RestTemplate}.
+ *
+ * @param provider the {@link VaultEndpoint} provider.
+ * @return {@code this} {@link RestTemplateBuilder}.
+ */
+ public RestTemplateBuilder endpointProvider(VaultEndpointProvider provider) {
+
+ Assert.notNull(provider, "VaultEndpointProvider must not be null");
+
+ this.endpointProvider = provider;
+
+ return this;
+ }
+
+ /**
+ * Set the {@link ClientHttpRequestFactory} that should be used with the
+ * {@link RestTemplate}.
+ *
+ * @param requestFactory the request factory.
+ * @return {@code this} {@link RestTemplateBuilder}.
+ */
+ public RestTemplateBuilder requestFactory(ClientHttpRequestFactory requestFactory) {
+
+ Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
+
+ return requestFactory(() -> requestFactory);
+ }
+
+ /**
+ * Set the {@link Supplier} of {@link ClientHttpRequestFactory} that should be called
+ * each time we {@link #build()} a new {@link RestTemplate} instance.
+ *
+ * @param requestFactory the supplier for the request factory.
+ * @return {@code this} {@link RestTemplateBuilder}.
+ */
+ public RestTemplateBuilder requestFactory(
+ Supplier requestFactory) {
+
+ Assert.notNull(requestFactory,
+ "Supplier of ClientHttpRequestFactory must not be null");
+
+ this.requestFactory = requestFactory;
+ return this;
+ }
+
+ /**
+ * Set the {@link ResponseErrorHandler} that should be used with the
+ * {@link RestTemplate}.
+ *
+ * @param errorHandler the error handler to use.
+ * @return {@code this} {@link RestTemplateBuilder}.
+ */
+ public RestTemplateBuilder errorHandler(ResponseErrorHandler errorHandler) {
+
+ Assert.notNull(errorHandler, "ErrorHandler must not be null");
+
+ this.errorHandler = errorHandler;
+ return this;
+ }
+
+ /**
+ * Add a default header that will be set if not already present on the outgoing
+ * {@link HttpRequest}.
+ *
+ * @param name the name of the header.
+ * @param value the header value.
+ * @return {@code this} {@link RestTemplateBuilder}.
+ */
+ public RestTemplateBuilder defaultHeader(String name, String value) {
+
+ Assert.hasText(name, "Header name must not be null or empty");
+
+ this.defaultHeaders.put(name, value);
+
+ return this;
+ }
+
+ /**
+ * Add the {@link RestTemplateCustomizer RestTemplateCustomizers} that should be
+ * applied to the {@link RestTemplate}. Customizers are applied in the order that they
+ * were added.
+ *
+ * @param customizer the template customizers to add.
+ * @return {@code this} {@link RestTemplateBuilder}.
+ */
+ public RestTemplateBuilder customizer(RestTemplateCustomizer... customizer) {
+
+ this.customizers.addAll(Arrays.asList(customizer));
+
+ return this;
+ }
+
+ /**
+ * Add the {@link RestTemplateRequestCustomizer RestTemplateRequestCustomizers} that
+ * should be applied to the {@link ClientHttpRequest}. Customizers are applied in the
+ * order that they were added.
+ *
+ * @param requestCustomizers the request customizers to add.
+ * @return {@code this} {@link RestTemplateBuilder}.
+ */
+ @SuppressWarnings("unchecked")
+ public RestTemplateBuilder requestCustomizers(
+ RestTemplateRequestCustomizer>... requestCustomizers) {
+
+ Assert.notNull(requestCustomizers, "RequestCustomizers must not be null");
+
+ this.requestCustomizers.addAll((List) Arrays.asList(requestCustomizers));
+ return this;
+ }
+
+ /**
+ * Build a new {@link RestTemplate}. {@link VaultEndpoint} must be set.
+ *
+ * Applies also {@link ResponseErrorHandler} and {@link RestTemplateCustomizer} if
+ * configured.
+ *
+ * @return a new {@link RestTemplate}.
+ */
+ public RestTemplate build() {
+
+ Assert.state(this.endpointProvider != null,
+ "VaultEndpointProvider must not be null");
+
+ RestTemplate restTemplate = createTemplate();
+
+ if (errorHandler != null) {
+ restTemplate.setErrorHandler(errorHandler);
+ }
+
+ customizers.forEach(customizer -> customizer.customize(restTemplate));
+
+ return restTemplate;
+ }
+
+ /**
+ * Create the {@link RestTemplate} to use.
+ *
+ * @return the {@link RestTemplate} to use.
+ */
+ protected RestTemplate createTemplate() {
+
+ ClientHttpRequestFactory requestFactory = this.requestFactory.get();
+ RestTemplateBuilderClientHttpRequestFactoryWrapper wrapper = new RestTemplateBuilderClientHttpRequestFactoryWrapper(
+ requestFactory, new LinkedHashMap<>(defaultHeaders), new LinkedHashSet<>(
+ requestCustomizers));
+
+ return VaultClients.createRestTemplate(endpointProvider, wrapper);
+ }
+
+ static class RestTemplateBuilderClientHttpRequestFactoryWrapper extends
+ AbstractClientHttpRequestFactoryWrapper {
+
+ private final Map defaultHeaders;
+
+ private final Set> requestCustomizers;
+
+ RestTemplateBuilderClientHttpRequestFactoryWrapper(
+ ClientHttpRequestFactory requestFactory,
+ Map defaultHeaders,
+ Set> requestCustomizers) {
+
+ super(requestFactory);
+ this.defaultHeaders = defaultHeaders;
+ this.requestCustomizers = requestCustomizers;
+ }
+
+ @Override
+ protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod,
+ ClientHttpRequestFactory requestFactory) throws IOException {
+
+ ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
+ HttpHeaders headers = request.getHeaders();
+
+ this.defaultHeaders.forEach(headers::addIfAbsent);
+ this.requestCustomizers.forEach(it -> it.customize(request));
+
+ return request;
+ }
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateCustomizer.java b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateCustomizer.java
new file mode 100644
index 00000000..971304c2
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateCustomizer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.client;
+
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * Callback interface that can be used to customize a {@link RestTemplate}.
+ *
+ * @author Mark Paluch
+ * @since 2.2
+ * @see RestTemplateBuilder
+ */
+@FunctionalInterface
+public interface RestTemplateCustomizer {
+
+ /**
+ * Callback to customize a {@link RestTemplate} instance.
+ *
+ * @param restTemplate the template to customize.
+ */
+ void customize(RestTemplate restTemplate);
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateRequestCustomizer.java b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateRequestCustomizer.java
new file mode 100644
index 00000000..72136403
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateRequestCustomizer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.client;
+
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * Callback interface that can be used to customize the {@link ClientHttpRequest} sent
+ * from a {@link RestTemplate}.
+ *
+ * @param the {@link ClientHttpRequest} type.
+ * @since 2.2
+ * @see RestTemplateBuilder
+ */
+@FunctionalInterface
+public interface RestTemplateRequestCustomizer {
+
+ /**
+ * Customize the specified {@link ClientHttpRequest}.
+ *
+ * @param request the request to customize.
+ */
+ void customize(T request);
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientBuilder.java b/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientBuilder.java
new file mode 100644
index 00000000..4a9ba5a1
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientBuilder.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.client;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.reactive.ClientHttpConnector;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.vault.support.ClientOptions;
+import org.springframework.vault.support.SslConfiguration;
+import org.springframework.web.reactive.function.client.ClientRequest;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * Builder that can be used to configure and create a {@link WebClient}. Provides
+ * convenience methods to configure {@link #httpConnector(ClientHttpConnector)
+ * ClientHttpConnector} and {@link #defaultHeader(String, String) default headers}.
+ *
+ * By default the built {@link WebClient} will attempt to use the most suitable
+ * {@link ClientHttpConnector} using {@link ClientHttpConnectorFactory#create}.
+ *
+ * @author Mark Paluch
+ * @since 2.2
+ * @see ClientHttpConnectorFactory
+ * @see WebClientCustomizer
+ */
+public class WebClientBuilder {
+
+ private @Nullable VaultEndpointProvider endpointProvider;
+
+ private Supplier httpConnector = () -> ClientHttpConnectorFactory
+ .create(new ClientOptions(), SslConfiguration.unconfigured());
+
+ private final Map defaultHeaders = new LinkedHashMap<>();
+
+ private final List customizers = new ArrayList<>();
+
+ private final Set filterFunctions = new LinkedHashSet<>();
+
+ private WebClientBuilder() {
+ }
+
+ /**
+ * Create a new {@link WebClientBuilder}.
+ *
+ * @return a new {@link WebClientBuilder}.
+ */
+ public static WebClientBuilder builder() {
+ return new WebClientBuilder();
+ }
+
+ /**
+ * Set the {@link VaultEndpoint} that should with the {@link WebClient}.
+ *
+ * @param endpoint the {@link VaultEndpoint} provider.
+ * @return {@code this} {@link WebClientBuilder}.
+ */
+ public WebClientBuilder endpoint(VaultEndpoint endpoint) {
+ return endpointProvider(SimpleVaultEndpointProvider.of(endpoint));
+ }
+
+ /**
+ * Set the {@link VaultEndpointProvider} that should with the {@link WebClient}.
+ *
+ * @param provider the {@link VaultEndpoint} provider.
+ * @return {@code this} {@link WebClientBuilder}.
+ */
+ public WebClientBuilder endpointProvider(VaultEndpointProvider provider) {
+
+ Assert.notNull(provider, "VaultEndpointProvider must not be null");
+
+ this.endpointProvider = provider;
+
+ return this;
+ }
+
+ /**
+ * Set the {@link ClientHttpConnector} that should be used each with the
+ * {@link WebClient}.
+ *
+ * @param httpConnector the HTTP connector.
+ * @return {@code this} {@link WebClientBuilder}.
+ */
+ public WebClientBuilder httpConnector(ClientHttpConnector httpConnector) {
+
+ Assert.notNull(httpConnector, "ClientHttpConnector must not be null");
+
+ return requestFactory(() -> httpConnector);
+ }
+
+ /**
+ * Set the {@link Supplier} of {@link ClientHttpConnector} that should be called each
+ * time we {@link #build()} a new {@link WebClient} instance.
+ *
+ * @param httpConnector the supplier for the HTTP connector.
+ * @return {@code this} {@link WebClientBuilder}.
+ */
+ public WebClientBuilder requestFactory(Supplier httpConnector) {
+
+ Assert.notNull(httpConnector, "Supplier of ClientHttpConnector must not be null");
+
+ this.httpConnector = httpConnector;
+ return this;
+ }
+
+ /**
+ * Add a default header that will be set if not already present on the outgoing
+ * {@link HttpRequest}.
+ *
+ * @param name the name of the header.
+ * @param value the header value.
+ * @return {@code this} {@link WebClientBuilder}.
+ */
+ public WebClientBuilder defaultHeader(String name, String value) {
+
+ Assert.hasText(name, "Header name must not be null or empty");
+
+ this.defaultHeaders.put(name, value);
+
+ return this;
+ }
+
+ /**
+ * Add the {@link WebClientCustomizer WebClientCustomizers} that should be applied to
+ * the {@link WebClient}. Customizers are applied in the order that they were added.
+ *
+ * @param customizer the client customizers to add.
+ * @return {@code this} {@link WebClientBuilder}.
+ */
+ public WebClientBuilder customizer(WebClientCustomizer... customizer) {
+
+ this.customizers.addAll(Arrays.asList(customizer));
+
+ return this;
+ }
+
+ /**
+ * Add the {@link ExchangeFilterFunction ExchangeFilterFunctions} that should be
+ * applied to the {@link ClientRequest}. {@link ExchangeFilterFunction}s are applied
+ * in the order that they were added.
+ *
+ * @param filterFunctions the request customizers to add.
+ * @return {@code this} {@link WebClientBuilder}.
+ */
+ public WebClientBuilder filter(ExchangeFilterFunction... filterFunctions) {
+
+ Assert.notNull(filterFunctions, "ExchangeFilterFunctions must not be null");
+
+ this.filterFunctions.addAll(Arrays.asList(filterFunctions));
+ return this;
+ }
+
+ /**
+ * Build a new {@link WebClient}. {@link VaultEndpoint} must be set.
+ *
+ * Applies also {@link ExchangeFilterFunction} and {@link WebClientCustomizer} if
+ * configured.
+ *
+ * @return a new {@link WebClient}.
+ */
+ public WebClient build() {
+
+ Assert.state(this.endpointProvider != null,
+ "VaultEndpointProvider must not be null");
+
+ WebClient.Builder builder = createWebClientBuilder();
+
+ if (!defaultHeaders.isEmpty()) {
+
+ Map defaultHeaders = this.defaultHeaders;
+ builder.filter((request, next) -> {
+
+ return next.exchange(ClientRequest.from(request)
+ .headers(headers -> defaultHeaders.forEach(headers::addIfAbsent))
+ .build());
+
+ });
+ }
+
+ builder.filters(exchangeFilterFunctions -> exchangeFilterFunctions
+ .addAll(this.filterFunctions));
+
+ customizers.forEach(customizer -> customizer.customize(builder));
+
+ return builder.build();
+ }
+
+ /**
+ * Create the {@link WebClient.Builder} to use.
+ *
+ * @return the {@link WebClient.Builder} to use.
+ */
+ protected WebClient.Builder createWebClientBuilder() {
+
+ ClientHttpConnector connector = this.httpConnector.get();
+
+ return ReactiveVaultClients.createWebClientBuilder(endpointProvider, connector);
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientCustomizer.java b/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientCustomizer.java
new file mode 100644
index 00000000..102505ca
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientCustomizer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.client;
+
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * Callback interface that can be used to customize a {@link WebClient.Builder}.
+ *
+ * @author Mark Paluch
+ * @since 2.2
+ * @see WebClientBuilder
+ */
+@FunctionalInterface
+public interface WebClientCustomizer {
+
+ /**
+ * Callback to customize a {@link WebClient.Builder} instance.
+ *
+ * @param webClientBuilder the client builder to customize.
+ */
+ void customize(WebClient.Builder webClientBuilder);
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java
index a0992afd..5f45b6ce 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java
@@ -33,7 +33,10 @@ import org.springframework.vault.authentication.ReactiveSessionManager;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.authentication.VaultTokenSupplier;
+import org.springframework.vault.client.ClientHttpConnectorFactory;
import org.springframework.vault.client.ReactiveVaultClients;
+import org.springframework.vault.client.VaultEndpointProvider;
+import org.springframework.vault.client.WebClientBuilder;
import org.springframework.vault.core.ReactiveVaultTemplate;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.VaultToken;
@@ -62,6 +65,21 @@ import org.springframework.web.reactive.function.client.WebClient;
public abstract class AbstractReactiveVaultConfiguration extends
AbstractVaultConfiguration {
+ /**
+ * Create a {@link WebClientBuilder} initialized with {@link VaultEndpointProvider}
+ * and {@link ClientHttpConnector}. May be overridden by subclasses.
+ *
+ * @return the {@link WebClientBuilder}.
+ * @see #vaultEndpointProvider()
+ * @see #clientHttpConnector()
+ * @since 2.2
+ */
+ protected WebClientBuilder webClientBuilder(VaultEndpointProvider endpointProvider,
+ ClientHttpConnector httpConnector) {
+ return WebClientBuilder.builder().endpointProvider(endpointProvider)
+ .httpConnector(httpConnector);
+ }
+
/**
* Create a {@link ReactiveVaultTemplate}.
*
@@ -72,8 +90,8 @@ public abstract class AbstractReactiveVaultConfiguration extends
*/
@Bean
public ReactiveVaultTemplate reactiveVaultTemplate() {
- return new ReactiveVaultTemplate(vaultEndpoint(), clientHttpConnector(),
- reactiveSessionManager());
+ return new ReactiveVaultTemplate(webClientBuilder(vaultEndpointProvider(),
+ clientHttpConnector()), reactiveSessionManager());
}
/**
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java
index 6d3b168f..8bd06f29 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java
@@ -30,6 +30,8 @@ import org.springframework.util.Assert;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.LifecycleAwareSessionManager;
import org.springframework.vault.authentication.SessionManager;
+import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
+import org.springframework.vault.client.RestTemplateBuilder;
import org.springframework.vault.client.SimpleVaultEndpointProvider;
import org.springframework.vault.client.VaultClients;
import org.springframework.vault.client.VaultEndpoint;
@@ -75,6 +77,22 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw
*/
public abstract ClientAuthentication clientAuthentication();
+ /**
+ * Create a {@link RestTemplateBuilder} initialized with {@link VaultEndpointProvider}
+ * and {@link ClientHttpRequestFactory}. May be overridden by subclasses.
+ *
+ * @return the {@link RestTemplateBuilder}.
+ * @see #vaultEndpointProvider()
+ * @see #clientHttpRequestFactoryWrapper()
+ * @since 2.2
+ */
+ protected RestTemplateBuilder restTemplateBuilder(
+ VaultEndpointProvider endpointProvider,
+ ClientHttpRequestFactory requestFactory) {
+ return RestTemplateBuilder.builder().endpointProvider(endpointProvider)
+ .requestFactory(requestFactory);
+ }
+
/**
* Create a {@link VaultTemplate}.
*
@@ -85,8 +103,8 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw
*/
@Bean
public VaultTemplate vaultTemplate() {
- return new VaultTemplate(vaultEndpointProvider(),
- clientHttpRequestFactoryWrapper().getClientHttpRequestFactory(),
+ return new VaultTemplate(restTemplateBuilder(vaultEndpointProvider(),
+ clientHttpRequestFactoryWrapper().getClientHttpRequestFactory()),
sessionManager());
}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpConnectorFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpConnectorFactory.java
index cf88409f..2ed3ee75 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpConnectorFactory.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpConnectorFactory.java
@@ -15,29 +15,22 @@
*/
package org.springframework.vault.config;
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-
-import io.netty.channel.ChannelOption;
-import io.netty.handler.ssl.SslContextBuilder;
-import reactor.netty.http.client.HttpClient;
-
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
-import static org.springframework.vault.config.ClientHttpRequestFactoryFactory.createKeyManagerFactory;
-import static org.springframework.vault.config.ClientHttpRequestFactoryFactory.createTrustManagerFactory;
-import static org.springframework.vault.config.ClientHttpRequestFactoryFactory.hasSslConfiguration;
-
/**
* Factory for {@link ClientHttpConnector} that supports
* {@link ReactorClientHttpConnector}.
*
* @author Mark Paluch
* @since 2.0
+ * @deprecated since 2.2, use
+ * {@link org.springframework.vault.client.ClientHttpConnectorFactory} as the
+ * functionality was moved to the {@code org.springframework.vault.client} package.
*/
+@Deprecated
public class ClientHttpConnectorFactory {
/**
@@ -50,44 +43,7 @@ public class ClientHttpConnectorFactory {
*/
public static ClientHttpConnector create(ClientOptions options,
SslConfiguration sslConfiguration) {
-
- HttpClient client = HttpClient.create();
-
- if (hasSslConfiguration(sslConfiguration)) {
-
- SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
- configureSsl(sslConfiguration, sslContextBuilder);
-
- client = client.secure(builder -> {
- builder.sslContext(sslContextBuilder);
- });
- }
-
- client = client.tcpConfiguration(it -> it.option(
- ChannelOption.CONNECT_TIMEOUT_MILLIS,
- Math.toIntExact(options.getConnectionTimeout().toMillis())));
-
- return new ReactorClientHttpConnector(client);
- }
-
- private static void configureSsl(SslConfiguration sslConfiguration,
- SslContextBuilder sslContextBuilder) {
-
- try {
-
- if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
- sslContextBuilder.trustManager(createTrustManagerFactory(sslConfiguration
- .getTrustStoreConfiguration()));
- }
-
- if (sslConfiguration.getKeyStoreConfiguration().isPresent()) {
- sslContextBuilder.keyManager(createKeyManagerFactory(
- sslConfiguration.getKeyStoreConfiguration(),
- sslConfiguration.getKeyConfiguration()));
- }
- }
- catch (GeneralSecurityException | IOException e) {
- throw new IllegalStateException(e);
- }
+ return org.springframework.vault.client.ClientHttpConnectorFactory.create(
+ options, sslConfiguration);
}
}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpRequestFactoryFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpRequestFactoryFactory.java
index fb3206ca..fbbd241e 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpRequestFactoryFactory.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/config/ClientHttpRequestFactoryFactory.java
@@ -15,60 +15,9 @@
*/
package org.springframework.vault.config;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.ProxySelector;
-import java.net.Socket;
-import java.security.GeneralSecurityException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.security.PrivateKey;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.KeyManagerFactorySpi;
-import javax.net.ssl.ManagerFactoryParameters;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509ExtendedKeyManager;
-import javax.net.ssl.X509TrustManager;
-
-import io.netty.handler.ssl.SslContextBuilder;
-import io.netty.handler.ssl.SslProvider;
-import okhttp3.OkHttpClient.Builder;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.impl.client.LaxRedirectStrategy;
-import org.apache.http.impl.conn.DefaultSchemePortResolver;
-import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
-
import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
-import org.springframework.http.client.Netty4ClientHttpRequestFactory;
-import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
-import org.springframework.http.client.SimpleClientHttpRequestFactory;
-import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
-import org.springframework.util.StringUtils;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
-import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
-
-import static org.springframework.vault.support.SslConfiguration.KeyConfiguration;
/**
* Factory for {@link ClientHttpRequestFactory} that supports Apache HTTP Components,
@@ -76,39 +25,13 @@ import static org.springframework.vault.support.SslConfiguration.KeyConfiguratio
* {@link ClientHttpRequestFactory} depending on the available dependencies.
*
* @author Mark Paluch
+ * @deprecated since 2.2, use
+ * {@link org.springframework.vault.client.ClientHttpRequestFactoryFactory} as the
+ * functionality was moved to the {@code org.springframework.vault.client} package.
*/
+@Deprecated
public class ClientHttpRequestFactoryFactory {
- private static final Log logger = LogFactory
- .getLog(ClientHttpRequestFactoryFactory.class);
-
- private static final boolean HTTP_COMPONENTS_PRESENT = isPresent("org.apache.http.client.HttpClient");
-
- private static final boolean OKHTTP3_PRESENT = isPresent("okhttp3.OkHttpClient");
-
- private static final boolean NETTY_PRESENT = isPresent(
- "io.netty.channel.nio.NioEventLoopGroup", "io.netty.handler.ssl.SslContext",
- "io.netty.handler.codec.http.HttpClientCodec");
-
- /**
- * Checks for presence of all {@code classNames} using this class' classloader.
- *
- * @param classNames
- * @return {@literal true} if all classes are present; {@literal false} if at least
- * one class cannot be found.
- */
- private static boolean isPresent(String... classNames) {
-
- for (String className : classNames) {
- if (!ClassUtils.isPresent(className,
- ClientHttpRequestFactoryFactory.class.getClassLoader())) {
- return false;
- }
- }
-
- return true;
- }
-
/**
* Create a {@link ClientHttpRequestFactory} for the given {@link ClientOptions} and
* {@link SslConfiguration}.
@@ -120,336 +43,7 @@ public class ClientHttpRequestFactoryFactory {
*/
public static ClientHttpRequestFactory create(ClientOptions options,
SslConfiguration sslConfiguration) {
-
- Assert.notNull(options, "ClientOptions must not be null");
- Assert.notNull(sslConfiguration, "SslConfiguration must not be null");
-
- try {
-
- if (HTTP_COMPONENTS_PRESENT) {
- return HttpComponents.usingHttpComponents(options, sslConfiguration);
- }
-
- if (OKHTTP3_PRESENT) {
- return OkHttp3.usingOkHttp3(options, sslConfiguration);
- }
-
- if (NETTY_PRESENT) {
- return Netty.usingNetty(options, sslConfiguration);
- }
- }
- catch (GeneralSecurityException e) {
- throw new IllegalStateException(e);
- }
- catch (IOException e) {
- throw new IllegalStateException(e);
- }
-
- if (hasSslConfiguration(sslConfiguration)) {
- logger.warn("VaultProperties has SSL configured but the SSL configuration "
- + "must be applied outside the Vault Client to use the JDK HTTP client");
- }
-
- return new SimpleClientHttpRequestFactory();
- }
-
- static SSLContext getSSLContext(SslConfiguration sslConfiguration,
- TrustManager[] trustManagers) throws GeneralSecurityException, IOException {
-
- KeyConfiguration keyConfiguration = sslConfiguration.getKeyConfiguration();
- KeyManager[] keyManagers = sslConfiguration.getKeyStoreConfiguration()
- .isPresent() ? createKeyManagerFactory(
- sslConfiguration.getKeyStoreConfiguration(), keyConfiguration)
- .getKeyManagers() : null;
-
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(keyManagers, trustManagers, null);
-
- return sslContext;
- }
-
- private static TrustManager[] getTrustManagers(SslConfiguration sslConfiguration)
- throws GeneralSecurityException, IOException {
-
- return sslConfiguration.getTrustStoreConfiguration().isPresent() ? createTrustManagerFactory(
- sslConfiguration.getTrustStoreConfiguration()).getTrustManagers()
- : null;
- }
-
- static KeyManagerFactory createKeyManagerFactory(
- KeyStoreConfiguration keyStoreConfiguration, KeyConfiguration keyConfiguration)
- throws GeneralSecurityException, IOException {
-
- KeyStore keyStore = KeyStore.getInstance(StringUtils
- .hasText(keyStoreConfiguration.getStoreType()) ? keyStoreConfiguration
- .getStoreType() : KeyStore.getDefaultType());
-
- loadKeyStore(keyStoreConfiguration, keyStore);
-
- KeyManagerFactory keyManagerFactory = KeyManagerFactory
- .getInstance(KeyManagerFactory.getDefaultAlgorithm());
-
- char[] keyPasswordToUse = keyConfiguration.getKeyPassword();
-
- if (keyPasswordToUse == null) {
- keyPasswordToUse = keyStoreConfiguration.getStorePassword() == null ? new char[0]
- : keyStoreConfiguration.getStorePassword();
- }
-
- keyManagerFactory.init(keyStore, keyPasswordToUse);
-
- if (StringUtils.hasText(keyConfiguration.getKeyAlias())) {
- return new KeySelectingKeyManagerFactory(keyManagerFactory, keyConfiguration);
- }
-
- return keyManagerFactory;
- }
-
- static TrustManagerFactory createTrustManagerFactory(
- KeyStoreConfiguration keyStoreConfiguration) throws GeneralSecurityException,
- IOException {
-
- KeyStore trustStore = KeyStore.getInstance(StringUtils
- .hasText(keyStoreConfiguration.getStoreType()) ? keyStoreConfiguration
- .getStoreType() : KeyStore.getDefaultType());
-
- loadKeyStore(keyStoreConfiguration, trustStore);
-
- TrustManagerFactory trustManagerFactory = TrustManagerFactory
- .getInstance(TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init(trustStore);
-
- return trustManagerFactory;
- }
-
- private static void loadKeyStore(KeyStoreConfiguration keyStoreConfiguration,
- KeyStore keyStore) throws IOException, NoSuchAlgorithmException,
- CertificateException {
-
- InputStream inputStream = null;
- try {
- inputStream = keyStoreConfiguration.getResource().getInputStream();
- keyStore.load(inputStream, keyStoreConfiguration.getStorePassword());
- }
- finally {
- if (inputStream != null) {
- inputStream.close();
- }
- }
- }
-
- static boolean hasSslConfiguration(SslConfiguration sslConfiguration) {
- return sslConfiguration.getTrustStoreConfiguration().isPresent()
- || sslConfiguration.getKeyStoreConfiguration().isPresent();
- }
-
- /**
- * {@link ClientHttpRequestFactory} for Apache Http Components.
- *
- * @author Mark Paluch
- */
- static class HttpComponents {
-
- static ClientHttpRequestFactory usingHttpComponents(ClientOptions options,
- SslConfiguration sslConfiguration) throws GeneralSecurityException,
- IOException {
-
- HttpClientBuilder httpClientBuilder = HttpClients.custom();
-
- httpClientBuilder.setRoutePlanner(new SystemDefaultRoutePlanner(
- DefaultSchemePortResolver.INSTANCE, ProxySelector.getDefault()));
-
- if (hasSslConfiguration(sslConfiguration)) {
-
- SSLContext sslContext = getSSLContext(sslConfiguration,
- getTrustManagers(sslConfiguration));
- SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
- sslContext);
- httpClientBuilder.setSSLSocketFactory(sslSocketFactory);
- httpClientBuilder.setSSLContext(sslContext);
- }
-
- RequestConfig requestConfig = RequestConfig
- .custom()
- //
- .setConnectTimeout(
- Math.toIntExact(options.getConnectionTimeout().toMillis())) //
- .setSocketTimeout(
- Math.toIntExact(options.getReadTimeout().toMillis())) //
- .setAuthenticationEnabled(true) //
- .build();
-
- httpClientBuilder.setDefaultRequestConfig(requestConfig);
-
- // Support redirects
- httpClientBuilder.setRedirectStrategy(new LaxRedirectStrategy());
-
- return new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
- }
- }
-
- /**
- * {@link ClientHttpRequestFactory} for the {@link okhttp3.OkHttpClient}.
- *
- * @author Mark Paluch
- */
- static class OkHttp3 {
-
- static ClientHttpRequestFactory usingOkHttp3(ClientOptions options,
- SslConfiguration sslConfiguration) throws GeneralSecurityException,
- IOException {
-
- Builder builder = new Builder();
-
- if (hasSslConfiguration(sslConfiguration)) {
-
- TrustManager[] trustManagers = getTrustManagers(sslConfiguration);
-
- if (trustManagers.length != 1
- || !(trustManagers[0] instanceof X509TrustManager)) {
- throw new IllegalStateException("Unexpected default trust managers:"
- + Arrays.toString(trustManagers));
- }
-
- X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
- SSLContext sslContext = getSSLContext(sslConfiguration, trustManagers);
-
- builder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
- }
-
- builder.connectTimeout(options.getConnectionTimeout().toMillis(),
- TimeUnit.MILLISECONDS).readTimeout(
- options.getReadTimeout().toMillis(), TimeUnit.MILLISECONDS);
-
- return new OkHttp3ClientHttpRequestFactory(builder.build());
- }
- }
-
- /**
- * {@link ClientHttpRequestFactory} for Netty.
- *
- * @author Mark Paluch
- */
- static class Netty {
-
- static ClientHttpRequestFactory usingNetty(ClientOptions options,
- SslConfiguration sslConfiguration) throws GeneralSecurityException,
- IOException {
-
- final Netty4ClientHttpRequestFactory requestFactory = new Netty4ClientHttpRequestFactory();
-
- if (hasSslConfiguration(sslConfiguration)) {
-
- SslContextBuilder sslContextBuilder = SslContextBuilder //
- .forClient();
-
- if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
- sslContextBuilder
- .trustManager(createTrustManagerFactory(sslConfiguration
- .getTrustStoreConfiguration()));
- }
-
- if (sslConfiguration.getKeyStoreConfiguration().isPresent()) {
- sslContextBuilder.keyManager(createKeyManagerFactory(
- sslConfiguration.getKeyStoreConfiguration(),
- sslConfiguration.getKeyConfiguration()));
- }
-
- requestFactory.setSslContext(sslContextBuilder.sslProvider(
- SslProvider.JDK).build());
- }
-
- requestFactory.setConnectTimeout(Math.toIntExact(options
- .getConnectionTimeout().toMillis()));
- requestFactory.setReadTimeout(Math.toIntExact(options.getReadTimeout()
- .toMillis()));
-
- return requestFactory;
- }
- }
-
- static class KeySelectingKeyManagerFactory extends KeyManagerFactory {
-
- KeySelectingKeyManagerFactory(KeyManagerFactory factory,
- KeyConfiguration keyConfiguration) {
- super(new KeyManagerFactorySpi() {
- @Override
- protected void engineInit(KeyStore keyStore, char[] chars)
- throws KeyStoreException, NoSuchAlgorithmException,
- UnrecoverableKeyException {
- factory.init(keyStore, chars);
- }
-
- @Override
- protected void engineInit(
- ManagerFactoryParameters managerFactoryParameters)
- throws InvalidAlgorithmParameterException {
- factory.init(managerFactoryParameters);
- }
-
- @Override
- protected KeyManager[] engineGetKeyManagers() {
-
- KeyManager[] keyManagers = factory.getKeyManagers();
-
- if (keyManagers.length == 1
- && keyManagers[0] instanceof X509ExtendedKeyManager) {
-
- return new KeyManager[] { new KeySelectingX509KeyManager(
- (X509ExtendedKeyManager) keyManagers[0], keyConfiguration) };
- }
-
- return keyManagers;
- }
- }, factory.getProvider(), factory.getAlgorithm());
- }
- }
-
- private static class KeySelectingX509KeyManager extends X509ExtendedKeyManager {
-
- private final X509ExtendedKeyManager delegate;
- private final KeyConfiguration keyConfiguration;
-
- KeySelectingX509KeyManager(X509ExtendedKeyManager delegate,
- KeyConfiguration keyConfiguration) {
- this.delegate = delegate;
- this.keyConfiguration = keyConfiguration;
- }
-
- @Override
- public String[] getClientAliases(String keyType, Principal[] issuers) {
- return delegate.getClientAliases(keyType, issuers);
- }
-
- @Override
- public String chooseClientAlias(String[] keyType, Principal[] issuers,
- Socket socket) {
- return keyConfiguration.getKeyAlias();
- }
-
- public String chooseEngineClientAlias(String[] keyType, Principal[] issuers,
- SSLEngine engine) {
- return keyConfiguration.getKeyAlias();
- }
-
- @Override
- public String[] getServerAliases(String keyType, Principal[] issuers) {
- return delegate.getServerAliases(keyType, issuers);
- }
-
- @Override
- public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
- return delegate.chooseServerAlias(keyType, issuers, socket);
- }
-
- @Override
- public X509Certificate[] getCertificateChain(String alias) {
- return delegate.getCertificateChain(alias);
- }
-
- @Override
- public PrivateKey getPrivateKey(String alias) {
- return delegate.getPrivateKey(alias);
- }
+ return org.springframework.vault.client.ClientHttpRequestFactoryFactory.create(
+ options, sslConfiguration);
}
}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java
index d7c1b464..b16666f9 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultTemplate.java
@@ -31,12 +31,12 @@ import org.springframework.util.Assert;
import org.springframework.vault.VaultException;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.VaultTokenSupplier;
-import org.springframework.vault.client.ReactiveVaultClients;
import org.springframework.vault.client.SimpleVaultEndpointProvider;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.client.VaultHttpHeaders;
import org.springframework.vault.client.VaultResponses;
+import org.springframework.vault.client.WebClientBuilder;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultResponseSupport;
import org.springframework.web.client.HttpStatusCodeException;
@@ -92,13 +92,33 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations {
Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null");
Assert.notNull(connector, "ClientHttpConnector must not be null");
- Assert.notNull(vaultTokenSupplier, "AuthenticationSupplier must not be null");
+ Assert.notNull(vaultTokenSupplier, "VaultTokenSupplier must not be null");
this.vaultTokenSupplier = vaultTokenSupplier;
this.statelessClient = doCreateWebClient(endpointProvider, connector);
this.sessionClient = doCreateSessionWebClient(endpointProvider, connector);
}
+ /**
+ * Create a new {@link ReactiveVaultTemplate} through a {@link WebClientBuilder}, and
+ * {@link VaultTokenSupplier}.
+ *
+ * @param webClientBuilder must not be {@literal null}.
+ * @param vaultTokenSupplier must not be {@literal null}
+ * @since 2.2
+ */
+ public ReactiveVaultTemplate(WebClientBuilder webClientBuilder,
+ VaultTokenSupplier vaultTokenSupplier) {
+
+ Assert.notNull(webClientBuilder, "WebClientBuilder must not be null");
+ Assert.notNull(vaultTokenSupplier, "VaultTokenSupplier must not be null");
+
+ this.vaultTokenSupplier = vaultTokenSupplier;
+ this.statelessClient = webClientBuilder.build();
+ this.sessionClient = webClientBuilder.build().mutate().filter(getSessionFilter())
+ .build();
+ }
+
/**
* Create a {@link WebClient} to be used by {@link ReactiveVaultTemplate} for Vault
* communication given {@link VaultEndpointProvider} and {@link ClientHttpConnector}.
@@ -117,7 +137,8 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations {
Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null");
Assert.notNull(connector, "ClientHttpConnector must not be null");
- return ReactiveVaultClients.createWebClient(endpointProvider, connector);
+ return WebClientBuilder.builder().httpConnector(connector)
+ .endpointProvider(endpointProvider).build();
}
/**
@@ -139,16 +160,21 @@ public class ReactiveVaultTemplate implements ReactiveVaultOperations {
Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null");
Assert.notNull(connector, "ClientHttpConnector must not be null");
- ExchangeFilterFunction filter = ofRequestProcessor(request -> vaultTokenSupplier
- .getVaultToken().map(token -> {
+ ExchangeFilterFunction filter = getSessionFilter();
+
+ return WebClientBuilder.builder().httpConnector(connector)
+ .endpointProvider(endpointProvider).filter(filter).build();
+ }
+
+ private ExchangeFilterFunction getSessionFilter() {
+
+ return ofRequestProcessor(request -> vaultTokenSupplier.getVaultToken().map(
+ token -> {
return ClientRequest.from(request).headers(headers -> {
headers.set(VaultHttpHeaders.VAULT_TOKEN, token.getToken());
}).build();
}));
-
- return doCreateWebClient(endpointProvider, connector).mutate().filter(filter)
- .build();
}
@Override
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java
index 9672cdfb..f2df327c 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java
@@ -25,14 +25,15 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.SessionManager;
import org.springframework.vault.authentication.SimpleSessionManager;
+import org.springframework.vault.client.RestTemplateBuilder;
import org.springframework.vault.client.SimpleVaultEndpointProvider;
-import org.springframework.vault.client.VaultClients;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.client.VaultEndpointProvider;
import org.springframework.vault.client.VaultHttpHeaders;
@@ -124,6 +125,28 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
this.sessionTemplate = doCreateSessionTemplate(endpointProvider, requestFactory);
}
+ /**
+ * Create a new {@link VaultTemplate} through a {@link RestTemplateBuilder} and
+ * {@link SessionManager}.
+ *
+ * @param restTemplateBuilder must not be {@literal null}.
+ * @param sessionManager must not be {@literal null}.
+ * @since 2.2
+ */
+ public VaultTemplate(RestTemplateBuilder restTemplateBuilder,
+ SessionManager sessionManager) {
+
+ Assert.notNull(restTemplateBuilder, "RestTemplateBuilder must not be null");
+ Assert.notNull(sessionManager, "SessionManager must not be null");
+
+ this.sessionManager = sessionManager;
+ this.dedicatedSessionManager = false;
+
+ this.statelessTemplate = restTemplateBuilder.build();
+ this.sessionTemplate = restTemplateBuilder.build();
+ this.sessionTemplate.getInterceptors().add(getSessionInterceptor());
+ }
+
/**
* Create a {@link RestTemplate} to be used by {@link VaultTemplate} for Vault
* communication given {@link VaultEndpointProvider} and
@@ -139,10 +162,8 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
protected RestTemplate doCreateRestTemplate(VaultEndpointProvider endpointProvider,
ClientHttpRequestFactory requestFactory) {
- Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null");
- Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
-
- return VaultClients.createRestTemplate(endpointProvider, requestFactory);
+ return RestTemplateBuilder.builder().endpointProvider(endpointProvider)
+ .requestFactory(requestFactory).build();
}
/**
@@ -162,23 +183,26 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
VaultEndpointProvider endpointProvider,
ClientHttpRequestFactory requestFactory) {
- Assert.notNull(endpointProvider, "VaultEndpointProvider must not be null");
- Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
+ return RestTemplateBuilder
+ .builder()
+ .endpointProvider(endpointProvider)
+ .requestFactory(requestFactory)
+ .customizer(
+ restTemplate -> restTemplate.getInterceptors().add(
+ getSessionInterceptor())).build();
+ }
- RestTemplate restTemplate = doCreateRestTemplate(endpointProvider, requestFactory);
+ private ClientHttpRequestInterceptor getSessionInterceptor() {
- restTemplate.getInterceptors().add(
- (request, body, execution) -> {
+ return (request, body, execution) -> {
- Assert.notNull(sessionManager, "SessionManager must not be null");
+ Assert.notNull(sessionManager, "SessionManager must not be null");
- request.getHeaders().add(VaultHttpHeaders.VAULT_TOKEN,
- sessionManager.getSessionToken().getToken());
+ request.getHeaders().add(VaultHttpHeaders.VAULT_TOKEN,
+ sessionManager.getSessionToken().getToken());
- return execution.execute(request, body);
- });
-
- return restTemplate;
+ return execution.execute(request, body);
+ };
}
/**
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java
index 934ffee8..f0a1ddc4 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/ClientCertificateAuthenticationIntegrationTests.java
@@ -19,8 +19,8 @@ import org.junit.Test;
import org.springframework.core.NestedRuntimeException;
import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
import org.springframework.vault.client.VaultClients;
-import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.VaultToken;
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/config/ClientHttpRequestFactoryFactoryIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpRequestFactoryFactoryIntegrationTests.java
similarity index 91%
rename from spring-vault-core/src/test/java/org/springframework/vault/config/ClientHttpRequestFactoryFactoryIntegrationTests.java
rename to spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpRequestFactoryFactoryIntegrationTests.java
index 9f6314cd..d9965e33 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/config/ClientHttpRequestFactoryFactoryIntegrationTests.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpRequestFactoryFactoryIntegrationTests.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.vault.config;
+package org.springframework.vault.client;
import org.junit.Test;
@@ -25,10 +25,9 @@ import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.Netty4ClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
-import org.springframework.vault.client.VaultEndpoint;
-import org.springframework.vault.config.ClientHttpRequestFactoryFactory.HttpComponents;
-import org.springframework.vault.config.ClientHttpRequestFactoryFactory.Netty;
-import org.springframework.vault.config.ClientHttpRequestFactoryFactory.OkHttp3;
+import org.springframework.vault.client.ClientHttpRequestFactoryFactory.HttpComponents;
+import org.springframework.vault.client.ClientHttpRequestFactoryFactory.Netty;
+import org.springframework.vault.client.ClientHttpRequestFactoryFactory.OkHttp3;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.util.Settings;
import org.springframework.web.client.HttpStatusCodeException;
@@ -95,8 +94,8 @@ public class ClientHttpRequestFactoryFactoryIntegrationTests {
// Uninitialized and sealed can cause status 500
try {
- ResponseEntity responseEntity = template.exchange(url, HttpMethod.GET,
- null, String.class);
+ ResponseEntity responseEntity = template.exchange(url,
+ HttpMethod.GET, null, String.class);
return responseEntity.getBody();
}
catch (HttpStatusCodeException e) {
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/client/RestTemplateBuilderUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/client/RestTemplateBuilderUnitTests.java
new file mode 100644
index 00000000..e32d64c0
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/client/RestTemplateBuilderUnitTests.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.vault.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collections;
+
+import org.junit.Test;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.web.client.DefaultResponseErrorHandler;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestTemplate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Unit tests for {@link RestTemplateBuilder}.
+ *
+ * @author Mark Paluch
+ */
+public class RestTemplateBuilderUnitTests {
+
+ @Test
+ public void shouldApplyErrorHandler() {
+
+ ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
+
+ RestTemplate restTemplate = RestTemplateBuilder.builder()
+ .endpoint(VaultEndpoint.create("localhost", 8200))
+ .errorHandler(errorHandler).build();
+
+ assertThat(restTemplate.getErrorHandler()).isSameAs(errorHandler);
+ }
+
+ @Test
+ public void shouldApplyErrorHandlerViaCustomizer() {
+
+ ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
+
+ RestTemplate restTemplate = RestTemplateBuilder.builder()
+ .endpoint(VaultEndpoint.create("localhost", 8200))
+ .customizer(it -> it.setErrorHandler(errorHandler)).build();
+
+ assertThat(restTemplate.getErrorHandler()).isSameAs(errorHandler);
+ }
+
+ @Test
+ public void shouldApplyDefaultHeaders() throws IOException {
+
+ RestTemplate restTemplate = RestTemplateBuilder.builder()
+ .endpoint(VaultEndpoint.create("localhost", 8200))
+ .defaultHeader("header", "value").build();
+
+ restTemplate.getInterceptors().clear();
+
+ ClientHttpRequest request = restTemplate.getRequestFactory().createRequest(
+ URI.create("/"), HttpMethod.GET);
+
+ assertThat(request.getHeaders()).containsEntry("header",
+ Collections.singletonList("value"));
+ }
+
+ @Test
+ public void shouldApplyRequestCustomizers() throws IOException {
+
+ RestTemplate restTemplate = RestTemplateBuilder
+ .builder()
+ .endpoint(VaultEndpoint.create("localhost", 8200))
+ .requestCustomizers(
+ request -> request.getHeaders().add("header", "value")).build();
+
+ restTemplate.getInterceptors().clear();
+
+ ClientHttpRequest request = restTemplate.getRequestFactory().createRequest(
+ URI.create("/"), HttpMethod.GET);
+
+ assertThat(request.getHeaders()).containsEntry("header",
+ Collections.singletonList("value"));
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/util/TestRestTemplateFactory.java b/spring-vault-core/src/test/java/org/springframework/vault/util/TestRestTemplateFactory.java
index abd42fad..a7b39e9a 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/util/TestRestTemplateFactory.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/util/TestRestTemplateFactory.java
@@ -21,9 +21,9 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.Assert;
+import org.springframework.vault.client.ClientHttpRequestFactoryFactory;
import org.springframework.vault.client.VaultClients;
import org.springframework.vault.client.VaultEndpoint;
-import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.web.client.RestTemplate;
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/util/TestWebClientFactory.java b/spring-vault-core/src/test/java/org/springframework/vault/util/TestWebClientFactory.java
index 69526ada..335345ea 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/util/TestWebClientFactory.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/util/TestWebClientFactory.java
@@ -17,9 +17,9 @@ package org.springframework.vault.util;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.util.Assert;
+import org.springframework.vault.client.ClientHttpConnectorFactory;
import org.springframework.vault.client.ReactiveVaultClients;
import org.springframework.vault.client.VaultEndpoint;
-import org.springframework.vault.config.ClientHttpConnectorFactory;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.web.reactive.function.client.WebClient;
@@ -46,8 +46,7 @@ public class TestWebClientFactory {
try {
ClientHttpConnector connector = ClientHttpConnectorFactory.create(
new ClientOptions(), sslConfiguration);
- return ReactiveVaultClients.createWebClient(TEST_VAULT_ENDPOINT,
- connector);
+ return ReactiveVaultClients.createWebClient(TEST_VAULT_ENDPOINT, connector);
}
catch (Exception e) {
throw new IllegalStateException(e);