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,38 @@ void withGlobalEventHubConnectionSetShouldConfigureShared() {
);
}

@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);
// Pin the shared consumer to the root builder: it must target base-eventhub,
// never the producer-dedicated override-eventhub. This is the actual #49245 invariant.
assertThat(context.getBean(EventHubConsumerClient.class).getEventHubName()).isEqualTo("base-eventhub");
assertThat(context.getBean(EventHubConsumerAsyncClient.class).getEventHubName()).isEqualTo("base-eventhub");
}
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,38 @@ void withGlobalEventHubConnectionSetShouldConfigureShared() {
);
}

@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);
// Pin the shared producer to the root builder: it must target base-eventhub,
// never the consumer-dedicated override-eventhub. This is the actual #49245 invariant.
assertThat(context.getBean(EventHubProducerClient.class).getEventHubName()).isEqualTo("base-eventhub");
assertThat(context.getBean(EventHubProducerAsyncClient.class).getEventHubName()).isEqualTo("base-eventhub");
}
Comment thread
rujche marked this conversation as resolved.
);
}

@Test
void withDedicatedEvenHubConnectionSetShouldConfigureDedicated() {
contextRunner
Expand Down