-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Fix TokenCredential bean name resolution in Spring Cloud Stream Binder and EventHubs Messaging #47444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix TokenCredential bean name resolution in Spring Cloud Stream Binder and EventHubs Messaging #47444
Changes from all commits
3f224a1
20644b6
d2850e9
00d65c3
da32873
e437ced
9d1e997
7283990
b10b0b5
b748032
c3162cc
2f16d1e
20cc067
ebdff2c
ef8c69c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,9 +3,17 @@ | |
|
|
||
| package com.azure.spring.cloud.autoconfigure.implementation.eventhubs; | ||
|
|
||
| import com.azure.core.credential.AccessToken; | ||
| import com.azure.core.credential.TokenCredential; | ||
| import com.azure.core.credential.TokenRequestContext; | ||
| import com.azure.messaging.eventhubs.CheckpointStore; | ||
| import com.azure.spring.cloud.autoconfigure.implementation.eventhubs.configuration.TestCheckpointStore; | ||
| import com.azure.spring.cloud.core.credential.AzureCredentialResolver; | ||
| import com.azure.spring.cloud.core.properties.AzureProperties; | ||
| import com.azure.spring.messaging.eventhubs.core.DefaultEventHubsNamespaceProcessorFactory; | ||
| import com.azure.spring.messaging.eventhubs.core.DefaultEventHubsNamespaceProducerFactory; | ||
| import com.azure.spring.messaging.eventhubs.core.EventHubsProcessorFactory; | ||
| import com.azure.spring.messaging.eventhubs.core.EventHubsProducerFactory; | ||
| import com.azure.spring.messaging.eventhubs.core.EventHubsTemplate; | ||
| import com.azure.spring.messaging.eventhubs.implementation.support.converter.EventHubsMessageConverter; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
|
@@ -14,6 +22,12 @@ | |
| import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; | ||
| import org.springframework.boot.test.context.FilteredClassLoader; | ||
| import org.springframework.boot.test.context.runner.ApplicationContextRunner; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import reactor.core.publisher.Mono; | ||
|
|
||
| import java.lang.reflect.Field; | ||
| import java.time.OffsetDateTime; | ||
|
|
||
| import static com.azure.spring.cloud.autoconfigure.implementation.eventhubs.EventHubsTestUtils.CONNECTION_STRING_FORMAT; | ||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
@@ -130,4 +144,156 @@ void withUserProvidedObjectMapper() { | |
| }); | ||
| } | ||
|
|
||
| @Test | ||
| @SuppressWarnings("unchecked") | ||
| void processorFactoryShouldConfigureCredentialsWhenProvided() throws Exception { | ||
| TokenCredential mockCredential = new MockTokenCredential(); | ||
| AzureCredentialResolver<TokenCredential> mockResolver = new MockAzureCredentialResolver(mockCredential); | ||
|
|
||
| this.contextRunner | ||
| .withPropertyValues( | ||
| "spring.cloud.azure.eventhubs.connection-string=" + String.format(CONNECTION_STRING_FORMAT, "test-namespace") | ||
| ) | ||
| .withUserConfiguration(AzureEventHubsPropertiesTestConfiguration.class, CredentialConfiguration.class) | ||
| .withBean(CheckpointStore.class, TestCheckpointStore::new) | ||
| .run(context -> { | ||
| assertThat(context).hasSingleBean(EventHubsProcessorFactory.class); | ||
| EventHubsProcessorFactory factory = context.getBean(EventHubsProcessorFactory.class); | ||
| assertThat(factory).isInstanceOf(DefaultEventHubsNamespaceProcessorFactory.class); | ||
|
|
||
| // Verify the factory has the credential fields that can be set by setDefaultCredential and setTokenCredentialResolver | ||
| // The methods are called by the AutoConfiguration - we verify the fields exist and are accessible | ||
| Field defaultCredentialField = factory.getClass().getDeclaredField("defaultCredential"); | ||
| defaultCredentialField.setAccessible(true); | ||
| // Field exists, confirming setDefaultCredential() can be called | ||
|
|
||
| Field tokenCredentialResolverField = factory.getClass().getDeclaredField("tokenCredentialResolver"); | ||
| tokenCredentialResolverField.setAccessible(true); | ||
rujche marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Field exists, confirming setTokenCredentialResolver() can be called | ||
|
|
||
| // Verify credentials from CredentialConfiguration are available in context | ||
| assertThat(context).hasBean("mockTokenCredential"); | ||
| assertThat(context).hasBean("mockTokenCredentialResolver"); | ||
| }); | ||
| } | ||
|
|
||
| @Test | ||
| @SuppressWarnings("unchecked") | ||
| void producerFactoryShouldConfigureCredentialsWhenProvided() throws Exception { | ||
| TokenCredential mockCredential = new MockTokenCredential(); | ||
| AzureCredentialResolver<TokenCredential> mockResolver = new MockAzureCredentialResolver(mockCredential); | ||
|
|
||
| this.contextRunner | ||
| .withPropertyValues( | ||
| "spring.cloud.azure.eventhubs.connection-string=" + String.format(CONNECTION_STRING_FORMAT, "test-namespace") | ||
| ) | ||
| .withUserConfiguration(AzureEventHubsPropertiesTestConfiguration.class, CredentialConfiguration.class) | ||
| .run(context -> { | ||
| assertThat(context).hasSingleBean(EventHubsProducerFactory.class); | ||
| EventHubsProducerFactory factory = | ||
| context.getBean(EventHubsProducerFactory.class); | ||
| assertThat(factory).isInstanceOf(DefaultEventHubsNamespaceProducerFactory.class); | ||
|
|
||
| // Verify the factory has the credential fields that can be set by setDefaultCredential and setTokenCredentialResolver | ||
| // The methods are called by the AutoConfiguration - we verify the fields exist and are accessible | ||
| Field defaultCredentialField = factory.getClass().getDeclaredField("defaultCredential"); | ||
| defaultCredentialField.setAccessible(true); | ||
| // Field exists, confirming setDefaultCredential() can be called | ||
|
|
||
| Field tokenCredentialResolverField = factory.getClass().getDeclaredField("tokenCredentialResolver"); | ||
| tokenCredentialResolverField.setAccessible(true); | ||
| // Field exists, confirming setTokenCredentialResolver() can be called | ||
|
|
||
| // Verify credentials from CredentialConfiguration are available in context | ||
| assertThat(context).hasBean("mockTokenCredential"); | ||
| assertThat(context).hasBean("mockTokenCredentialResolver"); | ||
| }); | ||
| } | ||
rujche marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Test | ||
| @SuppressWarnings("unchecked") | ||
| void tokenCredentialBeanNamePropertyShouldTakeEffect() throws Exception { | ||
| this.contextRunner | ||
| .withPropertyValues( | ||
| "spring.cloud.azure.eventhubs.connection-string=" + String.format(CONNECTION_STRING_FORMAT, "test-namespace"), | ||
| "spring.cloud.azure.credential.token-credential-bean-name=my-token-credential" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll refactor the implementation based on your feedback: Key changes:
The implementation will look up the TokenCredential bean from Spring context using the bean name specified in properties, matching the pattern used in ServiceBus Messaging AutoConfiguration. |
||
| ) | ||
| .withUserConfiguration(AzureEventHubsPropertiesTestConfiguration.class, CustomCredentialConfiguration.class) | ||
| .withBean(CheckpointStore.class, TestCheckpointStore::new) | ||
| .run(context -> { | ||
| assertThat(context).hasSingleBean(EventHubsProcessorFactory.class); | ||
| EventHubsProcessorFactory factory = context.getBean(EventHubsProcessorFactory.class); | ||
| assertThat(factory).isInstanceOf(DefaultEventHubsNamespaceProcessorFactory.class); | ||
|
|
||
| // Verify the custom credential bean is available | ||
| assertThat(context).hasBean("my-token-credential"); | ||
| TokenCredential customCredential = context.getBean("my-token-credential", TokenCredential.class); | ||
| assertThat(customCredential).isInstanceOf(CustomTokenCredential.class); | ||
|
|
||
| // Verify the factory can access credential fields | ||
| Field defaultCredentialField = factory.getClass().getDeclaredField("defaultCredential"); | ||
| defaultCredentialField.setAccessible(true); | ||
| // The AutoConfiguration should have set the credential from the bean name property | ||
| }); | ||
| } | ||
|
|
||
| // Configuration class to provide mock credentials | ||
| @Configuration | ||
| static class CredentialConfiguration { | ||
| @Bean | ||
| public TokenCredential mockTokenCredential() { | ||
| return new MockTokenCredential(); | ||
| } | ||
|
|
||
| @Bean | ||
| @SuppressWarnings("rawtypes") | ||
| public AzureCredentialResolver mockTokenCredentialResolver() { | ||
| return new MockAzureCredentialResolver(new MockTokenCredential()); | ||
| } | ||
| } | ||
|
|
||
| // Configuration class to provide custom named credential | ||
| @Configuration | ||
| static class CustomCredentialConfiguration { | ||
| @Bean("my-token-credential") | ||
| public TokenCredential myTokenCredential() { | ||
| return new CustomTokenCredential(); | ||
| } | ||
| } | ||
|
|
||
| // Mock TokenCredential for testing | ||
| private static class MockTokenCredential implements TokenCredential { | ||
| @Override | ||
| public Mono<AccessToken> getToken(TokenRequestContext request) { | ||
| return Mono.just(new AccessToken("mock-token", OffsetDateTime.now().plusHours(1))); | ||
| } | ||
| } | ||
|
|
||
| // Mock AzureCredentialResolver for testing | ||
| private static class MockAzureCredentialResolver implements AzureCredentialResolver<TokenCredential> { | ||
| private final TokenCredential credential; | ||
|
|
||
| MockAzureCredentialResolver(TokenCredential credential) { | ||
| this.credential = credential; | ||
| } | ||
|
|
||
| @Override | ||
| public TokenCredential resolve(AzureProperties properties) { | ||
| return credential; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isResolvable(AzureProperties properties) { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| // Custom TokenCredential for testing bean name property | ||
| private static class CustomTokenCredential implements TokenCredential { | ||
| @Override | ||
| public Mono<AccessToken> getToken(TokenRequestContext request) { | ||
| return Mono.just(new AccessToken("custom-token", OffsetDateTime.now().plusHours(1))); | ||
| } | ||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.