diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfiguration.java
index 1136b3aa..d4d281cd 100644
--- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfiguration.java
+++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfiguration.java
@@ -19,21 +19,34 @@ import java.net.URI;
import java.time.Duration;
import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.vault.config.VaultBootstrapConfiguration.TaskSchedulerWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.client.reactive.ClientHttpConnector;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.vault.authentication.AuthenticationStepsFactory;
import org.springframework.vault.authentication.AuthenticationStepsOperator;
import org.springframework.vault.authentication.CachingVaultTokenSupplier;
+import org.springframework.vault.authentication.ClientAuthentication;
+import org.springframework.vault.authentication.ReactiveLifecycleAwareSessionManager;
+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.ReactiveVaultClients;
import org.springframework.vault.client.VaultEndpoint;
@@ -47,12 +60,17 @@ import org.springframework.web.reactive.function.client.WebClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for reactive Spring Vault support.
+ *
+ * This auto-configuration only supports static endpoints without
+ * {@link org.springframework.vault.client.VaultEndpointProvider} support as endpoint
+ * providers could be potentially blocking implementations.
*
* @author Mark Paluch
* @since 2.0.0
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = true)
+@ConditionalOnExpression("${spring.cloud.vault.reactive.enabled:true}")
@ConditionalOnClass({ Flux.class, WebClient.class, ReactiveVaultOperations.class })
@EnableConfigurationProperties({ VaultProperties.class })
@Order(Ordered.LOWEST_PRECEDENCE - 10)
@@ -139,28 +157,105 @@ public class ReactiveVaultBootstrapConfiguration {
* Creates a {@link ReactiveVaultTemplate}.
*
* @return
- * @see #vaultTokenSupplier(AuthenticationStepsFactory)
+ * @see #reactiveVaultSessionManager(BeanFactory, ObjectFactory)
*/
@Bean
@ConditionalOnMissingBean
- public ReactiveVaultTemplate reactiveVaultTemplate(VaultTokenSupplier tokenSupplier) {
+ public ReactiveVaultTemplate reactiveVaultTemplate(
+ ReactiveSessionManager tokenSupplier) {
return new ReactiveVaultTemplate(vaultEndpoint, clientHttpConnector,
tokenSupplier);
}
/**
- * @return the {@link VaultTokenSupplier} for reactive Vault session management.
+ * @return {@link ReactiveSessionManager} for reactive session use.
+ * @see ReactiveSessionManager
+ * @see ReactiveLifecycleAwareSessionManager
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public ReactiveSessionManager reactiveVaultSessionManager(BeanFactory beanFactory,
+ ObjectFactory asyncTaskExecutorFactory) {
+
+ VaultTokenSupplier vaultTokenSupplier = beanFactory.getBean("vaultTokenSupplier",
+ VaultTokenSupplier.class);
+
+ if (vaultProperties.getConfig().getLifecycle().isEnabled()) {
+
+ WebClient webClient = ReactiveVaultClients.createWebClient(vaultEndpoint,
+ clientHttpConnector);
+ return new ReactiveLifecycleAwareSessionManager(vaultTokenSupplier,
+ asyncTaskExecutorFactory.getObject().getTaskScheduler(), webClient);
+ }
+
+ return CachingVaultTokenSupplier.of(vaultTokenSupplier);
+ }
+
+ /**
+ * @return {@link SessionManager} adapter wrapping {@link ReactiveSessionManager}.
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public SessionManager vaultSessionManager(ReactiveSessionManager sessionManager) {
+ return sessionManager.getSessionToken()::block;
+ }
+
+ /**
+ * @return the {@link VaultTokenSupplier} for reactive Vault session management
+ * adapting {@link ClientAuthentication} that also implement
+ * {@link AuthenticationStepsFactory}.
* @see AuthenticationStepsFactory
*/
@Bean
- @ConditionalOnMissingBean(VaultTokenSupplier.class)
- public VaultTokenSupplier vaultTokenSupplier(AuthenticationStepsFactory factory) {
+ @ConditionalOnMissingBean(name = "vaultTokenSupplier")
+ public VaultTokenSupplier vaultTokenSupplier(ListableBeanFactory beanFactory) {
+ Assert.notNull(beanFactory, "BeanFactory must not be null");
+
+ String[] authStepsFactories = beanFactory
+ .getBeanNamesForType(AuthenticationStepsFactory.class);
+
+ if (!ObjectUtils.isEmpty(authStepsFactories)) {
+
+ AuthenticationStepsFactory factory = beanFactory
+ .getBean(AuthenticationStepsFactory.class);
+ return createAuthenticationStepsOperator(factory);
+ }
+
+ String[] clientAuthentications = beanFactory
+ .getBeanNamesForType(ClientAuthentication.class);
+
+ if (!ObjectUtils.isEmpty(clientAuthentications)) {
+
+ ClientAuthentication clientAuthentication = beanFactory
+ .getBean(ClientAuthentication.class);
+
+ if (clientAuthentication instanceof TokenAuthentication) {
+
+ TokenAuthentication authentication = (TokenAuthentication) clientAuthentication;
+ return () -> Mono.just(authentication.login());
+ }
+
+ if (clientAuthentication instanceof AuthenticationStepsFactory) {
+ return createAuthenticationStepsOperator((AuthenticationStepsFactory) clientAuthentication);
+ }
+
+ throw new IllegalStateException(
+ String.format(
+ "Cannot construct VaultTokenSupplier from %s. "
+ + "ClientAuthentication must implement AuthenticationStepsFactory or be TokenAuthentication",
+ clientAuthentication));
+ }
+
+ throw new IllegalStateException(
+ "Cannot construct VaultTokenSupplier. Please configure VaultTokenSupplier bean named vaultTokenSupplier.");
+ }
+
+ private VaultTokenSupplier createAuthenticationStepsOperator(
+ AuthenticationStepsFactory factory) {
WebClient webClient = ReactiveVaultClients.createWebClient(this.vaultEndpoint,
this.clientHttpConnector);
- AuthenticationStepsOperator operator = new AuthenticationStepsOperator(
- factory.getAuthenticationSteps(), webClient);
-
- return CachingVaultTokenSupplier.of(operator);
+ return new AuthenticationStepsOperator(factory.getAuthenticationSteps(),
+ webClient);
}
}
diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java
index 1f6eb841..6f484b21 100644
--- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java
+++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/VaultBootstrapConfiguration.java
@@ -215,7 +215,7 @@ public class VaultBootstrapConfiguration implements InitializingBean {
*/
@Bean
@ConditionalOnMissingBean
- public SessionManager sessionManager(ClientAuthentication clientAuthentication,
+ public SessionManager vaultSessionManager(ClientAuthentication clientAuthentication,
ObjectFactory asyncTaskExecutorFactory) {
if (vaultProperties.getConfig().getLifecycle().isEnabled()) {
diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/PropertyNameTransformerUnitTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/PropertyNameTransformerUnitTests.java
index 09e43071..246881a2 100644
--- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/PropertyNameTransformerUnitTests.java
+++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/PropertyNameTransformerUnitTests.java
@@ -15,13 +15,13 @@
*/
package org.springframework.cloud.vault.config;
-import static org.assertj.core.api.Assertions.*;
-
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
+import static org.assertj.core.api.Assertions.*;
+
/**
* Unit tests for {@link PropertyNameTransformer}.
*
@@ -35,7 +35,7 @@ public class PropertyNameTransformerUnitTests {
PropertyNameTransformer transformer = new PropertyNameTransformer();
transformer.addKeyTransformation("old-key", "new-key");
- Map map = new HashMap<>();
+ Map map = new HashMap<>();
map.put("old-key", "value");
map.put("other-key", "other-value");
@@ -51,4 +51,4 @@ public class PropertyNameTransformerUnitTests {
assertThat(transformer.transformProperties(null)).isNull();
}
-}
\ No newline at end of file
+}
diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfigurationTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfigurationTests.java
index ef6b50ab..572ceea9 100644
--- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfigurationTests.java
+++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ReactiveVaultBootstrapConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 the original author or authors.
+ * Copyright 2017-2018 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.
@@ -24,6 +24,9 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.vault.authentication.AuthenticationSteps;
import org.springframework.vault.authentication.AuthenticationStepsFactory;
+import org.springframework.vault.authentication.LifecycleAwareSessionManager;
+import org.springframework.vault.authentication.SessionManager;
+import org.springframework.vault.authentication.SimpleSessionManager;
import org.springframework.vault.authentication.VaultTokenSupplier;
import org.springframework.vault.core.ReactiveVaultOperations;
import org.springframework.vault.support.VaultToken;
@@ -43,22 +46,42 @@ public class ReactiveVaultBootstrapConfigurationTests {
@Test
public void shouldConfigureTemplate() {
- load(AuthenticationFactoryConfiguration.class);
+ load(AuthenticationFactoryConfiguration.class,
+ "spring.cloud.vault.config.lifecycle.enabled=false");
assertThat(context.getBean(ReactiveVaultOperations.class)).isNotNull();
assertThat(context.getBean(AuthenticationStepsFactory.class)).isNotNull();
+ assertThat(context.getBean(SessionManager.class)).isNotNull()
+ .isNotInstanceOf(LifecycleAwareSessionManager.class)
+ .isNotInstanceOf(SimpleSessionManager.class);
assertThat(context.getBeanNamesForType(WebClient.class)).isEmpty();
}
@Test
public void shouldConfigureTemplateWithTokenSupplier() {
- load(TokeSupplierConfiguration.class);
+ load(TokenSupplierConfiguration.class,
+ "spring.cloud.vault.config.lifecycle.enabled=false");
assertThat(context.getBean(ReactiveVaultOperations.class)).isNotNull();
+ assertThat(context.getBean(SessionManager.class)).isNotNull()
+ .isNotInstanceOf(LifecycleAwareSessionManager.class)
+ .isNotInstanceOf(SimpleSessionManager.class);
assertThat(context.getBeanNamesForType(WebClient.class)).isEmpty();
}
+ @Test
+ public void shouldNotConfigureReactiveSupport() {
+
+ load(VaultBootstrapConfiguration.class,
+ "spring.cloud.vault.reactive.enabled=false",
+ "spring.cloud.vault.token=foo");
+
+ assertThat(context.getBeanNamesForType(ReactiveVaultOperations.class)).isEmpty();
+ assertThat(context.getBean(SessionManager.class)).isInstanceOf(
+ LifecycleAwareSessionManager.class);
+ }
+
private void load(Class> config, String... environment) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
@@ -82,7 +105,7 @@ public class ReactiveVaultBootstrapConfigurationTests {
}
@Configuration
- static class TokeSupplierConfiguration {
+ static class TokenSupplierConfiguration {
@Bean
VaultTokenSupplier vaultTokenSupplier() {