Skip to content
Merged
2 changes: 2 additions & 0 deletions sdk/spring/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ This section includes changes in `spring-cloud-azure-autoconfigure` module.
- AAD resource server now requires `spring.cloud.azure.active-directory.profile.tenant-id` to be set to a specific (non-reserved) tenant ID. Empty string, `common`, `organizations`, and `consumers` are no longer accepted and will cause application startup to fail with an `IllegalArgumentException`. ([#49033](https://github.com/Azure/azure-sdk-for-java/pull/49033))
- `AadAuthenticationFilter` now enables explicit audience validation by default. The filter will verify that the JWT's `aud` (audience) claim matches either `spring.cloud.azure.active-directory.credential.client-id` or `spring.cloud.azure.active-directory.app-id-uri`. Tokens issued for other applications will be rejected with `BadJWTException`. This prevents cross-application token reuse and aligns with OAuth2/OIDC security best practices. ([#49033](https://github.com/Azure/azure-sdk-for-java/pull/49033))
- B2C resource server now requires `spring.cloud.azure.active-directory.b2c.profile.tenant-id` to be set to a specific (non-reserved) tenant ID. Empty string, `common`, `organizations`, and `consumers` are no longer accepted. In addition, default token validation is hardened to enforce tenant-bound `tid`, stricter `aud` validation, and B2C-only trusted issuers. ([#49252](https://github.com/Azure/azure-sdk-for-java/pull/49252))
- Event Hubs auto-configuration now identifies the root `EventHubClientBuilder` by bean name (`springCloudAzureEventHubsClientBuilder`) instead of by type. To override the auto-configured root builder (and have shared `EventHubConsumerClient`/`EventHubProducerClient` use your bean), register the bean under the name `springCloudAzureEventHubsClientBuilder`. A user-supplied `EventHubClientBuilder` bean under a different name will no longer suppress the auto-configured root builder and will not be wired into the shared clients. ([#49245](https://github.com/Azure/azure-sdk-for-java/issues/49245))

#### Bugs Fixed

- Fixed Event Hubs autoconfiguration where a dedicated `EventHubClientBuilder` registered by `consumer`-only or `producer`-only sub-level overrides (`connection-string` / `namespace` / `event-hub-name`) suppressed the root builder and got injected into the opposite shared section, causing the shared client to target the other section's event hub. The root builder is now registered under bean name `springCloudAzureEventHubsClientBuilder` with a name-based `@ConditionalOnMissingBean`, and the shared consumer/producer sections gate on and inject that specific bean via `@Qualifier`. ([#49245](https://github.com/Azure/azure-sdk-for-java/issues/49245))
Comment thread
rujche marked this conversation as resolved.
- Fixed JDBC/Azure Database and Redis passwordless connection scope defaulting using the wrong `azure.scopes` value for Azure China and Azure US Government when `spring.cloud.azure.profile.cloud-type` is set to `azure_china` or `azure_us_government`. The scopes are now correctly derived from the merged cloud type. ([#47096](https://github.com/Azure/azure-sdk-for-java/issues/47096))

### Spring Cloud Azure Stream Binder Service Bus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ private AzureContextUtils() {
public static final String EVENT_HUB_CLIENT_BUILDER_FACTORY_BEAN_NAME =
"springCloudAzureEventHubsClientBuilderFactory";

/**
* Event Hubs client builder bean name.
*/
public static final String EVENT_HUB_CLIENT_BUILDER_BEAN_NAME =
"springCloudAzureEventHubsClientBuilder";

/**
* Event Hubs consumer client builder factory bean name.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME;
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_FACTORY_BEAN_NAME;

/**
Expand All @@ -39,15 +40,15 @@ class AzureEventHubsClientBuilderConfiguration {
this.eventHubsProperties = eventHubsProperties;
}

@Bean
@ConditionalOnMissingBean
@Bean(EVENT_HUB_CLIENT_BUILDER_BEAN_NAME)
@ConditionalOnMissingBean(name = EVENT_HUB_CLIENT_BUILDER_BEAN_NAME)
EventHubClientBuilder eventHubClientBuilder(@Qualifier(EVENT_HUB_CLIENT_BUILDER_FACTORY_BEAN_NAME)
EventHubClientBuilderFactory factory) {
return factory.build();
Comment thread
rujche marked this conversation as resolved.
Comment thread
rujche marked this conversation as resolved.
}

@Bean(EVENT_HUB_CLIENT_BUILDER_FACTORY_BEAN_NAME)
@ConditionalOnMissingBean
@ConditionalOnMissingBean(name = EVENT_HUB_CLIENT_BUILDER_FACTORY_BEAN_NAME)
EventHubClientBuilderFactory eventHubClientBuilderFactory(
Comment thread
rujche marked this conversation as resolved.
ObjectProvider<ServiceConnectionStringProvider<AzureServiceType.EventHubs>> connectionStringProviders,
ObjectProvider<AzureServiceClientBuilderCustomizer<EventHubClientBuilder>> customizers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME;
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CONSUMER_CLIENT_BUILDER_BEAN_NAME;
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CONSUMER_CLIENT_BUILDER_FACTORY_BEAN_NAME;

Expand All @@ -43,12 +44,13 @@
class AzureEventHubsConsumerClientConfiguration {

@ConditionalOnMissingProperty(prefix = "spring.cloud.azure.eventhubs.consumer", name = { "connection-string", "namespace", "event-hub-name" })
@ConditionalOnBean(EventHubClientBuilder.class)
@ConditionalOnBean(name = EVENT_HUB_CLIENT_BUILDER_BEAN_NAME)
@Configuration(proxyBeanMethods = false)
static class SharedConsumerConnectionConfiguration {

private final EventHubClientBuilder builder;
SharedConsumerConnectionConfiguration(AzureEventHubsProperties properties, EventHubClientBuilder builder) {
SharedConsumerConnectionConfiguration(AzureEventHubsProperties properties,
@Qualifier(EVENT_HUB_CLIENT_BUILDER_BEAN_NAME) EventHubClientBuilder builder) {
this.builder = builder;

PropertyMapper mapper = PropertyMapper.get();
Expand All @@ -64,7 +66,7 @@ EventHubConsumerAsyncClient eventHubConsumerAsyncClient() {

@Bean
@ConditionalOnMissingBean
EventHubConsumerClient eventHubConsumerClient(EventHubClientBuilder builder) {
EventHubConsumerClient eventHubConsumerClient() {
return this.builder.buildConsumerClient();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME;
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_PRODUCER_CLIENT_BUILDER_BEAN_NAME;
import static com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils.EVENT_HUB_PRODUCER_CLIENT_BUILDER_FACTORY_BEAN_NAME;

Expand All @@ -35,18 +36,18 @@
class AzureEventHubsProducerClientConfiguration {

@ConditionalOnMissingProperty(prefix = "spring.cloud.azure.eventhubs.producer", name = { "connection-string", "namespace", "event-hub-name" })
@ConditionalOnBean(EventHubClientBuilder.class)
@ConditionalOnBean(name = EVENT_HUB_CLIENT_BUILDER_BEAN_NAME)
@Configuration(proxyBeanMethods = false)
static class SharedProducerConnectionConfiguration {
@Bean
@ConditionalOnMissingBean
EventHubProducerAsyncClient eventHubProducerAsyncClient(EventHubClientBuilder builder) {
EventHubProducerAsyncClient eventHubProducerAsyncClient(@Qualifier(EVENT_HUB_CLIENT_BUILDER_BEAN_NAME) EventHubClientBuilder builder) {
Comment thread
rujche marked this conversation as resolved.
return builder.buildAsyncProducerClient();
}

@Bean
@ConditionalOnMissingBean
EventHubProducerClient eventHubProducerClient(EventHubClientBuilder builder) {
EventHubProducerClient eventHubProducerClient(@Qualifier(EVENT_HUB_CLIENT_BUILDER_BEAN_NAME) EventHubClientBuilder builder) {
Comment thread
rujche marked this conversation as resolved.
return builder.buildProducerClient();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.azure.data.appconfiguration.ConfigurationClientBuilder;
import com.azure.messaging.eventhubs.EventHubClientBuilder;
import com.azure.spring.cloud.autoconfigure.implementation.TestBuilderCustomizer;
import com.azure.spring.cloud.autoconfigure.implementation.context.AzureContextUtils;
import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties;
import com.azure.spring.cloud.autoconfigure.implementation.eventhubs.properties.AzureEventHubsConnectionDetails;
import com.azure.spring.cloud.core.provider.connectionstring.StaticConnectionStringProvider;
Expand Down Expand Up @@ -93,10 +94,24 @@ void userDefinedEventHubsClientBuilderProvidedShouldNotAutoconfigure() {
"spring.cloud.azure.eventhubs.connection-string=" + String.format(CONNECTION_STRING_FORMAT, "test-namespace")
)
.withBean(AzureGlobalProperties.class, AzureGlobalProperties::new)
.withBean("user-defined-builder", EventHubClientBuilder.class, EventHubClientBuilder::new)
.withBean(AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME, EventHubClientBuilder.class, EventHubClientBuilder::new)
Comment thread
rujche marked this conversation as resolved.
.run(context -> {
assertThat(context).hasSingleBean(EventHubClientBuilder.class);
assertThat(context).hasBean("user-defined-builder");
assertThat(context).hasBean(AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME);
});
}

@Test
void userDefinedEventHubsClientBuilderUnderCustomNameShouldNotSuppressAutoconfigure() {
this.contextRunner
.withPropertyValues(
"spring.cloud.azure.eventhubs.connection-string=" + String.format(CONNECTION_STRING_FORMAT, "test-namespace")
)
.withBean(AzureGlobalProperties.class, AzureGlobalProperties::new)
.withBean("user-defined-builder", EventHubClientBuilder.class, EventHubClientBuilder::new)
.run(context -> {
assertThat(context).getBeanNames(EventHubClientBuilder.class)
.containsExactlyInAnyOrder("user-defined-builder", AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ void withGlobalEventHubConnectionSetShouldConfigureShared() {
"spring.cloud.azure.eventhubs.consumer.consumer-group=" + consumerGroupName
)
.withUserConfiguration(AzureEventHubsPropertiesTestConfiguration.class)
.withBean(EventHubClientBuilder.class, () -> clientBuilder)
.withBean(AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME, EventHubClientBuilder.class, () -> clientBuilder)
Comment thread
rujche marked this conversation as resolved.
.run(
context -> {
assertThat(context).doesNotHaveBean(AzureEventHubsConsumerClientConfiguration.DedicatedConsumerConnectionConfiguration.class);
Expand All @@ -71,6 +71,60 @@ void withGlobalEventHubConnectionSetShouldConfigureShared() {
);
}

@Test
void producerOnlyDedicatedOverrideShouldNotActivateSharedConsumer() {
// Regression for issue #49245: producer dedicated builder must not satisfy the shared consumer condition,
// nor be injected into the shared consumer path. Without sub-level overrides on the consumer side and
// without the global event-hub-name, the consumer config simply should not activate.
contextRunner
.withPropertyValues(
"spring.cloud.azure.eventhubs.namespace=test-namespace",
"spring.cloud.azure.eventhubs.producer.event-hub-name=override-eventhub",
"spring.cloud.azure.eventhubs.consumer.consumer-group=test-consumer-group"
)
.withBean(AzureGlobalProperties.class, AzureGlobalProperties::new)
.withUserConfiguration(AzureEventHubsAutoConfiguration.class)
.run(
context -> {
assertThat(context).doesNotHaveBean(AzureEventHubsConsumerClientConfiguration.class);
assertThat(context).doesNotHaveBean(EventHubConsumerClient.class);
assertThat(context).doesNotHaveBean(EventHubConsumerAsyncClient.class);
// The producer dedicated path must actually be activated for this test to exercise the
// "asymmetric, dedicated builder bean exists" scenario the bug fix targets.
Comment thread
rujche marked this conversation as resolved.
Outdated
assertThat(context).hasSingleBean(AzureEventHubsProducerClientConfiguration.DedicatedProducerConnectionConfiguration.class);
assertThat(context).hasBean(AzureContextUtils.EVENT_HUB_PRODUCER_CLIENT_BUILDER_BEAN_NAME);
}
);
}

@Test
void sharedConsumerInjectsRootBuilderWhenProducerHasDedicatedOverride() {
// Regression for issue #49245: when both a global event-hub-name and a producer-only override exist,
// the shared consumer should still bind to the root builder, not the producer's dedicated builder.
contextRunner
.withPropertyValues(
"spring.cloud.azure.eventhubs.namespace=test-namespace",
"spring.cloud.azure.eventhubs.event-hub-name=base-eventhub",
"spring.cloud.azure.eventhubs.consumer.consumer-group=test-consumer-group",
"spring.cloud.azure.eventhubs.producer.event-hub-name=override-eventhub"
)
.withBean(AzureGlobalProperties.class, AzureGlobalProperties::new)
.withUserConfiguration(AzureEventHubsAutoConfiguration.class)
.run(
context -> {
assertThat(context).hasSingleBean(AzureEventHubsConsumerClientConfiguration.SharedConsumerConnectionConfiguration.class);
assertThat(context).doesNotHaveBean(AzureEventHubsConsumerClientConfiguration.DedicatedConsumerConnectionConfiguration.class);
Comment thread
rujche marked this conversation as resolved.
// Producer dedicated must be active so multiple EventHubClientBuilder beans coexist,
// proving the shared consumer is selecting the root builder by qualifier rather than
// succeeding by accident because only one builder bean exists.
assertThat(context).hasSingleBean(AzureEventHubsProducerClientConfiguration.DedicatedProducerConnectionConfiguration.class);
assertThat(context.getBeansOfType(EventHubClientBuilder.class)).hasSizeGreaterThan(1);
assertThat(context).hasBean(AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME);
assertThat(context).hasSingleBean(EventHubConsumerClient.class);
}
Comment thread
rujche marked this conversation as resolved.
);
}

@Test
void withDedicatedEvenHubConnectionSetShouldConfigureDedicated() {
contextRunner
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ void withGlobalEventHubConnectionSetShouldConfigureShared() {
"spring.cloud.azure.eventhubs.event-hub-name=" + eventHubName
)
.withUserConfiguration(AzureEventHubsPropertiesTestConfiguration.class)
.withBean(EventHubClientBuilder.class, () -> clientBuilder)
.withBean(AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME, EventHubClientBuilder.class, () -> clientBuilder)
Comment thread
rujche marked this conversation as resolved.
.run(
context -> {
assertThat(context).doesNotHaveBean(AzureEventHubsProducerClientConfiguration.DedicatedProducerConnectionConfiguration.class);
Expand All @@ -69,6 +69,60 @@ void withGlobalEventHubConnectionSetShouldConfigureShared() {
);
}

@Test
void consumerOnlyDedicatedOverrideShouldNotActivateSharedProducer() {
// Regression for issue #49245: a consumer-dedicated builder must not satisfy the shared producer condition.
// Without sub-level overrides on the producer side and without a global event-hub-name, the producer
// config simply should not activate.
contextRunner
Comment thread
rujche marked this conversation as resolved.
Outdated
.withPropertyValues(
"spring.cloud.azure.eventhubs.namespace=test-namespace",
"spring.cloud.azure.eventhubs.consumer.event-hub-name=override-eventhub",
"spring.cloud.azure.eventhubs.consumer.consumer-group=test-consumer-group"
)
.withBean(AzureGlobalProperties.class, AzureGlobalProperties::new)
.withUserConfiguration(AzureEventHubsAutoConfiguration.class)
.run(
context -> {
assertThat(context).doesNotHaveBean(AzureEventHubsProducerClientConfiguration.class);
assertThat(context).doesNotHaveBean(EventHubProducerClient.class);
assertThat(context).doesNotHaveBean(EventHubProducerAsyncClient.class);
// The consumer dedicated path must actually be activated for this test to exercise the
// "asymmetric, dedicated builder bean exists" scenario the bug fix targets.
assertThat(context).hasSingleBean(AzureEventHubsConsumerClientConfiguration.DedicatedConsumerConnectionConfiguration.class);
assertThat(context).hasBean(AzureContextUtils.EVENT_HUB_CONSUMER_CLIENT_BUILDER_BEAN_NAME);
}
);
}

@Test
void sharedProducerInjectsRootBuilderWhenConsumerHasDedicatedOverride() {
// Regression for issue #49245: when both a global event-hub-name and a consumer-only override exist,
// the shared producer should still bind to the root builder, not the consumer's dedicated builder.
contextRunner
.withPropertyValues(
"spring.cloud.azure.eventhubs.namespace=test-namespace",
"spring.cloud.azure.eventhubs.event-hub-name=base-eventhub",
"spring.cloud.azure.eventhubs.consumer.event-hub-name=override-eventhub",
"spring.cloud.azure.eventhubs.consumer.consumer-group=test-consumer-group"
)
.withBean(AzureGlobalProperties.class, AzureGlobalProperties::new)
.withUserConfiguration(AzureEventHubsAutoConfiguration.class)
.run(
context -> {
assertThat(context).hasSingleBean(AzureEventHubsProducerClientConfiguration.SharedProducerConnectionConfiguration.class);
assertThat(context).doesNotHaveBean(AzureEventHubsProducerClientConfiguration.DedicatedProducerConnectionConfiguration.class);
Comment thread
rujche marked this conversation as resolved.
// Consumer dedicated must be active so multiple EventHubClientBuilder beans coexist,
// proving the shared producer is selecting the root builder by qualifier rather than
// succeeding by accident because only one builder bean exists.
assertThat(context).hasSingleBean(AzureEventHubsConsumerClientConfiguration.DedicatedConsumerConnectionConfiguration.class);
assertThat(context.getBeansOfType(EventHubClientBuilder.class)).hasSizeGreaterThan(1);
assertThat(context).hasBean(AzureContextUtils.EVENT_HUB_CLIENT_BUILDER_BEAN_NAME);
assertThat(context).hasSingleBean(EventHubProducerClient.class);
}
Comment thread
rujche marked this conversation as resolved.
);
}

@Test
void withDedicatedEvenHubConnectionSetShouldConfigureDedicated() {
contextRunner
Expand Down
Loading