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);