configServices = cfEnv.findServicesByTag("configuration");
- if (configServices.size() != 1)
- return;
- CfCredentials credentials = configServices.stream().findFirst().get().getCredentials();
-
- registry.register(RestTemplate.class, context -> {
- String clientId = credentials.getString("client_id");
- String clientSecret = credentials.getString("client_secret");
- String accessTokenUri = credentials.getString("access_token_uri");
- RestTemplate restTemplate = new RestTemplate();
- ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("config-client")
- .clientId(clientId)
- .clientSecret(clientSecret)
- .tokenUri(accessTokenUri)
- .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
- .build();
- restTemplate.getInterceptors().add(new OAuth2AuthorizedClientHttpRequestInterceptor(clientRegistration));
- return restTemplate;
- });
- }
-
-}
diff --git a/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/ConfigClientOAuth2ConfigDataLocationResolver.java b/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/ConfigClientOAuth2ConfigDataLocationResolver.java
new file mode 100644
index 00000000..d24cd785
--- /dev/null
+++ b/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/ConfigClientOAuth2ConfigDataLocationResolver.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2023-2024 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 io.pivotal.spring.cloud.config.client;
+
+import org.apache.commons.logging.Log;
+import org.springframework.boot.BootstrapRegistry;
+import org.springframework.boot.context.config.*;
+import org.springframework.boot.logging.DeferredLogFactory;
+import org.springframework.cloud.config.client.ConfigClientProperties;
+import org.springframework.cloud.config.client.ConfigClientRequestTemplateFactory;
+import org.springframework.cloud.config.client.ConfigServerConfigDataLocationResolver;
+import org.springframework.cloud.config.client.ConfigServerConfigDataResource;
+import org.springframework.core.Ordered;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+
+/**
+ * Using oauth2 properties to configure an authorization interceptor for the
+ * RestTemplate
that calls config server.
+ *
+ * Note: Despite implementing {@link ConfigDataLocationResolver}, this class does not
+ * resolve any location. It only configures and registers the
+ * {@link ConfigClientRequestTemplateFactory} which later will be used by
+ * {@link ConfigServerConfigDataLocationResolver} to create RestTemplate
for
+ * calling config server.
+ *
+ * Finally, it registers the RestTemplate
bean to be consumed by
+ * {@link ConfigResourceClientAutoConfiguration} and
+ * {@link VaultTokenRenewalAutoConfiguration} after application startup.
+ */
+public class ConfigClientOAuth2ConfigDataLocationResolver
+ implements ConfigDataLocationResolver, Ordered {
+
+ private final Log log;
+
+ public ConfigClientOAuth2ConfigDataLocationResolver(DeferredLogFactory factory) {
+ this.log = factory.getLog(ConfigClientOAuth2ConfigDataLocationResolver.class);
+ }
+
+ @Override
+ public boolean isResolvable(ConfigDataLocationResolverContext resolverContext, ConfigDataLocation location) {
+ if (!location.hasPrefix(ConfigServerConfigDataLocationResolver.PREFIX)) {
+ return false;
+ }
+
+ var binder = resolverContext.getBinder();
+ var isConfigEnabled = binder.bind(ConfigClientProperties.PREFIX + ".enabled", Boolean.class).orElse(true);
+ if (!isConfigEnabled) {
+ return false;
+ }
+
+ var oAuth2Properties = binder.bind(ConfigClientOAuth2Properties.PREFIX, ConfigClientOAuth2Properties.class)
+ .orElse(null);
+ if (oAuth2Properties == null) {
+ log.warn("Config Client oauth2 properties are missing. Skipping the auth interceptor configuration");
+ return false;
+ }
+
+ var bootstrapContext = resolverContext.getBootstrapContext();
+
+ // Register the oauth2 properties
+ bootstrapContext.registerIfAbsent(ConfigClientOAuth2Properties.class,
+ BootstrapRegistry.InstanceSupplier.of(oAuth2Properties).withScope(BootstrapRegistry.Scope.PROTOTYPE));
+
+ // Register the custom factory with oauth2 interceptor.
+ bootstrapContext.registerIfAbsent(ConfigClientRequestTemplateFactory.class,
+ context -> new ConfigClientOAuth2RequestTemplateFactory(this.log,
+ context.get(ConfigClientProperties.class), oAuth2Properties));
+
+ bootstrapContext.addCloseListener(event -> {
+ var beanFactory = event.getApplicationContext().getBeanFactory();
+
+ // Add the RestTemplate as bean, once the startup is finished.
+ beanFactory.registerSingleton("configClientRestTemplate",
+ event.getBootstrapContext().get(RestTemplate.class));
+
+ // Add the OAuth2 Properties as bean, once the startup is finished.
+ beanFactory.registerSingleton("configClientOAuth2Properties",
+ event.getBootstrapContext().get(ConfigClientOAuth2Properties.class));
+ });
+
+ return false;
+ }
+
+ @Override
+ public List resolve(ConfigDataLocationResolverContext context,
+ ConfigDataLocation location)
+ throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
+ throw new IllegalStateException("Unexpected call. This resolver should not resolve any location");
+ }
+
+ @Override
+ public List resolveProfileSpecific(ConfigDataLocationResolverContext context,
+ ConfigDataLocation location, Profiles profiles) throws ConfigDataLocationNotFoundException {
+ throw new IllegalStateException("Unexpected call. This resolver should not resolve any location");
+ }
+
+ /**
+ * It should be registered before {@link ConfigServerConfigDataLocationResolver}. See
+ * {@link ConfigServerConfigDataLocationResolver#getOrder()}
+ */
+ @Override
+ public int getOrder() {
+ return -2;
+ }
+
+ private static class ConfigClientOAuth2RequestTemplateFactory extends ConfigClientRequestTemplateFactory {
+
+ private final ClientRegistration clientRegistration;
+
+ public ConfigClientOAuth2RequestTemplateFactory(Log log, ConfigClientProperties clientProperties,
+ ConfigClientOAuth2Properties oAuth2Properties) {
+ super(log, clientProperties);
+
+ this.clientRegistration = ClientRegistration.withRegistrationId("config-client")
+ .clientId(oAuth2Properties.getClientId())
+ .clientSecret(oAuth2Properties.getClientSecret())
+ .tokenUri(oAuth2Properties.getAccessTokenUri())
+ .scope(oAuth2Properties.getScope())
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+ .build();
+ }
+
+ @Override
+ public RestTemplate create() {
+ var restTemplate = super.create();
+ restTemplate.getInterceptors().add(new OAuth2AuthorizedClientHttpRequestInterceptor(clientRegistration));
+ return restTemplate;
+ }
+
+ }
+
+}
diff --git a/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/ConfigResourceClientAutoConfiguration.java b/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/ConfigResourceClientAutoConfiguration.java
index 1c991a87..32cc5b9e 100644
--- a/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/ConfigResourceClientAutoConfiguration.java
+++ b/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/ConfigResourceClientAutoConfiguration.java
@@ -17,15 +17,12 @@
package io.pivotal.spring.cloud.config.client;
import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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.config.client.ConfigClientAutoConfiguration;
import org.springframework.cloud.config.client.ConfigClientProperties;
import org.springframework.context.annotation.Bean;
-import org.springframework.security.oauth2.client.registration.ClientRegistration;
-import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.web.client.RestTemplate;
/**
@@ -35,25 +32,14 @@
*/
@AutoConfiguration(after = ConfigClientAutoConfiguration.class)
@ConditionalOnClass({ ConfigClientProperties.class })
-@EnableConfigurationProperties(ConfigClientOAuth2Properties.class)
public class ConfigResourceClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ConfigResourceClient.class)
- @ConditionalOnProperty(prefix = "spring.cloud.config.client.oauth2",
- name = { "client-id", "client-secret", "access-token-uri" })
- public ConfigResourceClient configResourceClient(ConfigClientProperties configClientProperties,
- ConfigClientOAuth2Properties configClientOAuth2Properties) {
- ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("config-client")
- .clientId(configClientOAuth2Properties.getClientId())
- .clientSecret(configClientOAuth2Properties.getClientSecret())
- .scope(configClientOAuth2Properties.getScope())
- .tokenUri(configClientOAuth2Properties.getAccessTokenUri())
- .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
- .build();
- RestTemplate restTemplate = new RestTemplate();
- restTemplate.getInterceptors().add(new OAuth2AuthorizedClientHttpRequestInterceptor(clientRegistration));
- return new OAuth2ConfigResourceClient(restTemplate, configClientProperties);
+ @ConditionalOnBean(value = RestTemplate.class, name = "configClientRestTemplate")
+ public ConfigResourceClient configResourceClient(RestTemplate configClientRestTemplate,
+ ConfigClientProperties configClientProperties) {
+ return new DefaultConfigResourceClient(configClientRestTemplate, configClientProperties);
}
}
diff --git a/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/OAuth2ConfigResourceClient.java b/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/DefaultConfigResourceClient.java
similarity index 96%
rename from spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/OAuth2ConfigResourceClient.java
rename to spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/DefaultConfigResourceClient.java
index 4b30c464..01456b4a 100644
--- a/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/OAuth2ConfigResourceClient.java
+++ b/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/DefaultConfigResourceClient.java
@@ -38,7 +38,7 @@
* @author Daniel Lavoie
* @author Anshul Mehra
*/
-class OAuth2ConfigResourceClient implements ConfigResourceClient {
+class DefaultConfigResourceClient implements ConfigResourceClient {
private enum ResourceType {
@@ -50,7 +50,7 @@ private enum ResourceType {
private final RestTemplate restTemplate;
- protected OAuth2ConfigResourceClient(RestTemplate restTemplate,
+ protected DefaultConfigResourceClient(RestTemplate restTemplate,
final ConfigClientProperties configClientProperties) {
this.restTemplate = restTemplate;
this.configClientProperties = configClientProperties;
diff --git a/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/VaultTokenRenewalAutoConfiguration.java b/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/VaultTokenRenewalAutoConfiguration.java
index 0577ac3a..ae6c1745 100644
--- a/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/VaultTokenRenewalAutoConfiguration.java
+++ b/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/VaultTokenRenewalAutoConfiguration.java
@@ -20,7 +20,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -34,85 +33,69 @@
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.security.oauth2.client.registration.ClientRegistration;
-import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
/**
* Configuration for a periodic Vault token renewer. Conditionally configured if there is
* a {@link ConfigClientProperties} bean and if there is a `spring.cloud.config.token`
- * property set.
- *
- * By default, the token is renewed every 60 seconds and is renewed with a 5 minute
- * time-to-live. The renew rate can be configured by setting `vault.token.renew.rate` to
- * some value that is the renewal rate in milliseconds. The renewal time-to-live can be
- * specified with by setting `vault.token.ttl` to some value indicating the time-to-live
- * in milliseconds.
+ * property set. By default, the token is renewed every 60 seconds and is renewed with a 5
+ * minute time-to-live. The renewal rate can be configured by setting
+ * `vault.token.renew.rate` to some value that is the renewal rate in milliseconds. The
+ * renewal time-to-live can be specified with by setting `vault.token.ttl` to some value
+ * indicating the time-to-live in milliseconds.
*
* @author cwalls
*/
@AutoConfiguration(after = ConfigClientAutoConfiguration.class)
@ConditionalOnBean(ConfigClientProperties.class)
-@ConditionalOnProperty(prefix = "spring.cloud.config",
- name = { "token", "client.oauth2.client-id", "client.oauth2.client-secret", "client.oauth2.access-token-uri" })
+@ConditionalOnProperty(name = "spring.cloud.config.token")
@EnableConfigurationProperties(ConfigClientOAuth2Properties.class)
@EnableScheduling
public class VaultTokenRenewalAutoConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(VaultTokenRenewalAutoConfiguration.class);
- @Bean
- public VaultTokenRefresher vaultTokenRefresher(ConfigClientProperties configClientProperties,
- ConfigClientOAuth2Properties configClientOAuth2Properties,
- @Qualifier("vaultTokenRenewal") RestTemplate restTemplate,
- @Value("${spring.cloud.config.token}") String vaultToken,
- // Default to a 300 second (5 minute) TTL
- @Value("${vault.token.ttl:300000}") long renewTtl) {
-
- var clientRegistration = ClientRegistration.withRegistrationId("config-client")
- .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
- .clientId(configClientOAuth2Properties.getClientId())
- .clientSecret(configClientOAuth2Properties.getClientSecret())
- .scope(configClientOAuth2Properties.getScope())
- .tokenUri(configClientOAuth2Properties.getAccessTokenUri())
- .build();
-
- restTemplate.getInterceptors().add(new OAuth2AuthorizedClientHttpRequestInterceptor(clientRegistration));
- // convert to seconds, since that's what Vault wants
- var request = buildTokenRenewRequest(vaultToken, renewTtl / 1000);
+ private static final String VAULT_TOKEN_HEADER = "X-Vault-Token";
- return new VaultTokenRefresher(restTemplate, obscuredToken(vaultToken), renewTtl,
- refreshUri(configClientProperties), request);
- }
+ private static final String REFRESH_PATH = "/vault/v1/auth/token/renew-self";
- @Bean("vaultTokenRenewal")
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
+ @Value("${spring.cloud.config.token}")
+ private String vaultToken;
- private String refreshUri(ConfigClientProperties configClientProperties) {
- return configClientProperties.getUri()[0] + "/vault/v1/auth/token/renew-self";
- }
+ // Default to a 300 second (5 minute) TTL
+ @Value("${vault.token.ttl:300000}")
+ long ttl;
+
+ @Bean
+ @ConditionalOnBean(value = RestTemplate.class, name = "configClientRestTemplate")
+ public VaultTokenRefresher vaultTokenRefresher(RestTemplate configClientRestTemplate,
+ ConfigClientProperties configClientProperties) {
- private String obscuredToken(String vaultToken) {
- return vaultToken.substring(0, 4) + "[*]" + vaultToken.substring(vaultToken.length() - 4);
+ var refreshUri = configClientProperties.getUri()[0] + REFRESH_PATH;
+ var obscuredToken = this.vaultToken.substring(0, 4) + "[*]"
+ + this.vaultToken.substring(this.vaultToken.length() - 4);
+
+ return new VaultTokenRefresher(configClientRestTemplate, obscuredToken, ttl, refreshUri,
+ buildTokenRenewRequest());
}
- private HttpEntity