Skip to content
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

Support passwordless connections for JMS ServiceBus in Spring #33489

Merged
merged 32 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d957828
jms passwordless
backwind1233 Feb 10, 2023
3a4737e
use namespace instead of endpoint
backwind1233 Feb 14, 2023
9888266
address some pipeline errors
backwind1233 Feb 14, 2023
be9940c
code refactor
backwind1233 Feb 14, 2023
df12f2e
update identity
backwind1233 Feb 14, 2023
16f7168
Merge branch 'main' into passwordless_jms
backwind1233 Feb 14, 2023
374d95f
use && not &
backwind1233 Feb 15, 2023
68069c6
address comments
backwind1233 Feb 15, 2023
3776c56
fix ut errors
backwind1233 Feb 15, 2023
7545cb8
fix it errors
backwind1233 Feb 15, 2023
8085fe4
fix checkstyle errors
backwind1233 Feb 15, 2023
ba71484
fix checkstyle errors
backwind1233 Feb 15, 2023
81023d6
1. code refactor
backwind1233 Feb 21, 2023
e6f4b91
merge main branch to current branch
backwind1233 Feb 21, 2023
005bf71
Fix pipeline errors
backwind1233 Feb 21, 2023
8309e45
add comment for PasswordlessProperties
backwind1233 Feb 22, 2023
c4e775e
add comment for PasswordlessProperties
backwind1233 Feb 22, 2023
22e3ac7
add more comments
backwind1233 Feb 22, 2023
614d520
Fix revApi
backwind1233 Feb 22, 2023
4082260
update metadata.json
backwind1233 Feb 22, 2023
aa1c1f2
replace core properties with autoconfigure properties
backwind1233 Feb 22, 2023
2e0554d
revert kafka passwordless codes
backwind1233 Feb 28, 2023
61ee897
fix some typos
backwind1233 Feb 28, 2023
d915384
add test case for AzurePasswordlessPropertiesUtils
backwind1233 Mar 1, 2023
99a1c5d
update java doc
backwind1233 Mar 1, 2023
115ec19
use ArrayList
backwind1233 Mar 2, 2023
ed2386d
fix jms bug
backwind1233 Mar 2, 2023
fbd531b
Merge branch 'main' into passwordless_jms
backwind1233 Mar 2, 2023
64bd4a3
update azure-sdk-bom version
backwind1233 Mar 2, 2023
ae1bce7
fix bug of ServiceBusJmsPasswordlessIT
backwind1233 Mar 2, 2023
97a6480
address some comments
backwind1233 Mar 3, 2023
db2691e
add some java docs
backwind1233 Mar 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@
"old": "method java\\.lang\\.String com\\.azure\\.spring\\.cloud\\.autoconfigure\\.jms\\.properties\\.AzureServiceBusJmsProperties::(getPassword|getRemoteUrl|getUsername)\\(\\)",
"justification": "Remove some meaningless jms properties"
},
{
"code" : "java.annotation.attributeValueChanged",
"old" : "class com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsAutoConfiguration",
"new" : "class com.azure.spring.cloud.autoconfigure.jms.ServiceBusJmsAutoConfiguration",
"justification": "Import ServiceBusJmsPasswordlessConfiguration.class"
},
{
"regex": true,
"code": "java\\.method\\.removed",
Expand Down
2 changes: 1 addition & 1 deletion sdk/boms/spring-cloud-azure-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-sdk-bom</artifactId>
<version>1.2.9</version> <!-- NOTE: This should be updated manually. -->
<version>1.2.10</version> <!-- NOTE: This should be updated manually. -->
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.cloud.autoconfigure.implementation.passwordless;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
* Add properties to 'spring.cloud.function.ineligible-definitions' to filter ineligible functions that used by passwordless autoconfigurations.
*
* @since 4.7.0
*/
public class AzurePasswordlessEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {

/**
* The order value of the {@link AzurePasswordlessEnvironmentPostProcessor}.
*/
public static final int ORDER = ConfigDataEnvironmentPostProcessor.ORDER + 2;

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Properties properties = new Properties();
List<String> passwordlessCredentialSupplier = new ArrayList<>();
passwordlessCredentialSupplier.add("azureRedisCredentialSupplier");
passwordlessCredentialSupplier.add("azureServiceBusJmsCredentialSupplier");
properties.setProperty("spring.cloud.function.ineligible-definitions", String.join(",", passwordlessCredentialSupplier));
environment.getPropertySources().addLast(new PropertiesPropertySource("passwordless", properties));
}

@Override
public int getOrder() {
return ORDER;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
package com.azure.spring.cloud.autoconfigure.jdbc;

import com.azure.core.credential.TokenCredential;
import com.azure.identity.extensions.implementation.credential.TokenCredentialProviderOptions;
import com.azure.identity.extensions.implementation.credential.provider.TokenCredentialProvider;
import com.azure.identity.extensions.implementation.enums.AuthProperty;
import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties;
import com.azure.spring.cloud.autoconfigure.implementation.jdbc.DatabaseType;
import com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcConnectionString;
import com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcConnectionStringEnhancer;
import com.azure.spring.cloud.core.implementation.credential.resolver.AzureTokenCredentialResolver;
import com.azure.spring.cloud.core.implementation.util.AzurePasswordlessPropertiesUtils;
import com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier;
import com.azure.spring.cloud.service.implementation.identity.credential.provider.SpringTokenCredentialProvider;
import com.azure.spring.cloud.service.implementation.passwordless.AzurePasswordlessProperties;
import com.azure.spring.cloud.service.implementation.passwordless.AzureJdbcPasswordlessProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
Expand All @@ -35,7 +37,6 @@
import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.POSTGRESQL_PROPERTY_NAME_APPLICATION_NAME;
import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.POSTGRESQL_PROPERTY_NAME_ASSUME_MIN_SERVER_VERSION;
import static com.azure.spring.cloud.autoconfigure.implementation.jdbc.JdbcPropertyConstants.POSTGRESQL_PROPERTY_VALUE_ASSUME_MIN_SERVER_VERSION;
import static com.azure.spring.cloud.core.implementation.util.AzurePropertiesUtils.copyPropertiesIgnoreTargetNonNull;
import static com.azure.spring.cloud.service.implementation.identity.credential.provider.SpringTokenCredentialProvider.PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME;


Expand All @@ -55,7 +56,7 @@ class JdbcPropertiesBeanPostProcessor implements BeanPostProcessor, EnvironmentA
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DataSourceProperties) {
DataSourceProperties dataSourceProperties = (DataSourceProperties) bean;
AzurePasswordlessProperties properties = buildAzureProperties();
AzureJdbcPasswordlessProperties properties = buildAzureProperties();

if (!properties.isPasswordlessEnabled()) {
LOGGER.debug("Feature passwordless authentication is not enabled, skip enhancing jdbc url.");
Expand Down Expand Up @@ -128,17 +129,15 @@ private void enhanceUserAgent(DatabaseType databaseType, JdbcConnectionStringEnh
}
}

private Map<String, String> buildEnhancedProperties(DatabaseType databaseType, AzurePasswordlessProperties properties) {
private Map<String, String> buildEnhancedProperties(DatabaseType databaseType, AzureJdbcPasswordlessProperties properties) {
Map<String, String> result = new HashMap<>();
AzureTokenCredentialResolver resolver = applicationContext.getBean(AzureTokenCredentialResolver.class);
TokenCredential tokenCredential = resolver.resolve(properties);
TokenCredentialProvider tokenCredentialProvider = TokenCredentialProvider.createDefault(new TokenCredentialProviderOptions(properties.toPasswordlessProperties()));
TokenCredential tokenCredential = tokenCredentialProvider.get();

if (tokenCredential != null) {
LOGGER.debug("Add SpringTokenCredentialProvider as the default token credential provider.");
AuthProperty.TOKEN_CREDENTIAL_BEAN_NAME.setProperty(result, PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME);
applicationContext.registerBean(PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME, TokenCredential.class, () -> tokenCredential);
}
AuthProperty.TOKEN_CREDENTIAL_BEAN_NAME.setProperty(result, PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME);
applicationContext.registerBean(PASSWORDLESS_TOKEN_CREDENTIAL_BEAN_NAME, TokenCredential.class, () -> tokenCredential);

LOGGER.debug("Add SpringTokenCredentialProvider as the default token credential provider.");
AuthProperty.TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME.setProperty(result, SPRING_TOKEN_CREDENTIAL_PROVIDER_CLASS_NAME);
AuthProperty.AUTHORITY_HOST.setProperty(result, properties.getProfile().getEnvironment().getActiveDirectoryEndpoint());

Expand All @@ -157,12 +156,14 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
this.applicationContext = (GenericApplicationContext) applicationContext;
}

private AzurePasswordlessProperties buildAzureProperties() {
private AzureJdbcPasswordlessProperties buildAzureProperties() {
AzureGlobalProperties azureGlobalProperties = applicationContext.getBean(AzureGlobalProperties.class);
AzurePasswordlessProperties azurePasswordlessProperties = Binder.get(environment)
.bindOrCreate(SPRING_CLOUD_AZURE_DATASOURCE_PREFIX, AzurePasswordlessProperties.class);
copyPropertiesIgnoreTargetNonNull(azureGlobalProperties.getProfile(), azurePasswordlessProperties.getProfile());
copyPropertiesIgnoreTargetNonNull(azureGlobalProperties.getCredential(), azurePasswordlessProperties.getCredential());
return azurePasswordlessProperties;
AzureJdbcPasswordlessProperties azurePasswordlessProperties = Binder.get(environment)
.bindOrCreate(SPRING_CLOUD_AZURE_DATASOURCE_PREFIX, AzureJdbcPasswordlessProperties.class);

AzureJdbcPasswordlessProperties mergedProperties = new AzureJdbcPasswordlessProperties();
AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(azureGlobalProperties, azurePasswordlessProperties, mergedProperties);
return mergedProperties;

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.cloud.autoconfigure.jms;

import com.azure.identity.extensions.implementation.template.AzureAuthenticationTemplate;

import java.util.Properties;
import java.util.function.Supplier;

/**
* AzureServiceBusJmsCredentialSupplier that provides a String as the password to connect Azure ServiceBus.
*
* @since 4.7.0
*/
public class AzureServiceBusJmsCredentialSupplier implements Supplier<String> {

private final AzureAuthenticationTemplate azureAuthenticationTemplate;

/**
* Create {@link AzureServiceBusJmsCredentialSupplier} instance.
* @param properties properties to initialize AzureServiceBusJmsCredentialSupplier.
*/
public AzureServiceBusJmsCredentialSupplier(Properties properties) {
azureAuthenticationTemplate = new AzureAuthenticationTemplate();
azureAuthenticationTemplate.init(properties);
}

@Override
public String get() {
return azureAuthenticationTemplate.getTokenAsPassword();
}

AzureServiceBusJmsCredentialSupplier(AzureAuthenticationTemplate azureAuthenticationTemplate) {
this.azureAuthenticationTemplate = azureAuthenticationTemplate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
package com.azure.spring.cloud.autoconfigure.jms;

import com.azure.spring.cloud.autoconfigure.condition.ConditionalOnMissingProperty;
import com.azure.spring.cloud.autoconfigure.context.AzureGlobalProperties;
import com.azure.spring.cloud.autoconfigure.jms.properties.AzureServiceBusJmsProperties;
import com.azure.spring.cloud.autoconfigure.resourcemanager.AzureServiceBusResourceManagerAutoConfiguration;
import com.azure.spring.cloud.core.implementation.util.AzurePasswordlessPropertiesUtils;
import com.azure.spring.cloud.core.provider.connectionstring.ServiceConnectionStringProvider;
import com.azure.spring.cloud.core.service.AzureServiceType;
import org.apache.qpid.jms.JmsConnectionExtensions;
Expand All @@ -31,6 +33,7 @@
import java.util.HashMap;
import java.util.Map;

import static com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier.AZURE_SPRING_PASSWORDLESS_SERVICE_BUS;
import static com.azure.spring.cloud.core.implementation.util.AzureSpringIdentifier.AZURE_SPRING_SERVICE_BUS;

/**
Expand All @@ -45,17 +48,27 @@
AzureServiceBusResourceManagerAutoConfiguration.class })
@ConditionalOnProperty(value = "spring.jms.servicebus.enabled", matchIfMissing = true)
@ConditionalOnClass({ ConnectionFactory.class, JmsConnectionFactory.class, JmsTemplate.class })
@EnableConfigurationProperties({ AzureServiceBusJmsProperties.class, JmsProperties.class })
@Import({ ServiceBusJmsConnectionFactoryConfiguration.class, ServiceBusJmsContainerConfiguration.class })
@EnableConfigurationProperties({ JmsProperties.class })
@Import({ ServiceBusJmsPasswordlessConfiguration.class, ServiceBusJmsConnectionFactoryConfiguration.class, ServiceBusJmsContainerConfiguration.class })
public class ServiceBusJmsAutoConfiguration {

@Bean
AzureServiceBusJmsProperties serviceBusJmsProperties(AzureGlobalProperties azureGlobalProperties) {
AzureServiceBusJmsProperties properties = new AzureServiceBusJmsProperties();
return mergeAzureProperties(azureGlobalProperties, properties);
}

@Bean
@ConditionalOnExpression("'premium'.equalsIgnoreCase('${spring.jms.servicebus.pricing-tier}')")
ServiceBusJmsConnectionFactoryCustomizer amqpOpenPropertiesCustomizer() {
ServiceBusJmsConnectionFactoryCustomizer amqpOpenPropertiesCustomizer(ObjectProvider<AzureServiceBusJmsCredentialSupplier> azureServiceBusJmsCredentialSupplier) {
return factory -> {
final Map<String, Object> properties = new HashMap<>();
properties.put("com.microsoft:is-client-provider", true);
properties.put("user-agent", AZURE_SPRING_SERVICE_BUS);
if (azureServiceBusJmsCredentialSupplier.getIfAvailable() != null) {
properties.put("user-agent", AZURE_SPRING_PASSWORDLESS_SERVICE_BUS);
} else {
properties.put("user-agent", AZURE_SPRING_SERVICE_BUS);
}
//set user agent
factory.setExtension(JmsConnectionExtensions.AMQP_OPEN_PROPERTIES.toString(),
(connection, uri) -> properties);
Expand All @@ -75,4 +88,10 @@ static AzureServiceBusJmsPropertiesBeanPostProcessor azureServiceBusJmsPropertie
ObjectProvider<ServiceConnectionStringProvider<AzureServiceType.ServiceBus>> connectionStringProviders) {
return new AzureServiceBusJmsPropertiesBeanPostProcessor(connectionStringProviders);
}

private AzureServiceBusJmsProperties mergeAzureProperties(AzureGlobalProperties azureGlobalProperties, AzureServiceBusJmsProperties azurePasswordlessProperties) {
AzureServiceBusJmsProperties mergedProperties = new AzureServiceBusJmsProperties();
AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(azureGlobalProperties, azurePasswordlessProperties, mergedProperties);
return mergedProperties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,26 @@ private <T extends ServiceBusJmsConnectionFactory> void setPrefetchPolicy(T fact
private <T extends ServiceBusJmsConnectionFactory> T createConnectionFactoryInstance(Class<T> factoryClass) {
try {
T factory;
ServiceBusConnectionString serviceBusConnectionString = new ServiceBusConnectionString(properties.getConnectionString());
String host = serviceBusConnectionString.getEndpointUri().getHost();
if (properties.isPasswordlessEnabled()) {
String remoteUrl = String.format(AMQP_URI_FORMAT,
properties.getNamespace() + "." + properties.getProfile().getEnvironment().getServiceBusDomainName(),
properties.getIdleTimeout().toMillis());
factory = factoryClass.getConstructor(String.class).newInstance(remoteUrl);
} else {
ServiceBusConnectionString serviceBusConnectionString = new ServiceBusConnectionString(properties.getConnectionString());
String host = serviceBusConnectionString.getEndpointUri().getHost();

String remoteUrl = String.format(AMQP_URI_FORMAT, host, properties.getIdleTimeout().toMillis());
String username = serviceBusConnectionString.getSharedAccessKeyName();
String password = serviceBusConnectionString.getSharedAccessKey();
String remoteUrl = String.format(AMQP_URI_FORMAT, host, properties.getIdleTimeout().toMillis());
String username = serviceBusConnectionString.getSharedAccessKeyName();
String password = serviceBusConnectionString.getSharedAccessKey();

if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
factory = factoryClass.getConstructor(String.class, String.class, String.class)
.newInstance(username, password, remoteUrl);
} else {
factory = factoryClass.getConstructor(String.class).newInstance(remoteUrl);
if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
factory = factoryClass.getConstructor(String.class, String.class, String.class)
.newInstance(username, password, remoteUrl);
} else {
factory = factoryClass.getConstructor(String.class).newInstance(remoteUrl);
}
}

return factory;
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
throw new IllegalStateException("Unable to create JmsConnectionFactory", ex);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.cloud.autoconfigure.jms;

import com.azure.spring.cloud.autoconfigure.jms.properties.AzureServiceBusJmsProperties;
import org.apache.qpid.jms.JmsConnectionExtensions;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* {@link EnableAutoConfiguration Auto-configuration} for Azure Service Bus JMS passwordless support.
*
* @since 4.7.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "spring.jms.servicebus.passwordless-enabled", havingValue = "true")
class ServiceBusJmsPasswordlessConfiguration {

@Bean
@ConditionalOnMissingBean
AzureServiceBusJmsCredentialSupplier azureServiceBusJmsCredentialSupplier(AzureServiceBusJmsProperties azureServiceBusJmsProperties) {
return new AzureServiceBusJmsCredentialSupplier(azureServiceBusJmsProperties.toPasswordlessProperties());
}

@Bean
ServiceBusJmsConnectionFactoryCustomizer jmsAADAuthenticationCustomizer(AzureServiceBusJmsCredentialSupplier credentialSupplier) {
return factory -> {
factory.setExtension(JmsConnectionExtensions.USERNAME_OVERRIDE.toString(), (connection, uri) -> "$jwt");
factory.setExtension(JmsConnectionExtensions.PASSWORD_OVERRIDE.toString(), (connection, uri) ->
credentialSupplier.get()
);
};
}


}
Loading