Skip to content

Commit

Permalink
Support Lettuce passwordless autoconfiguration (#40287)
Browse files Browse the repository at this point in the history
* Support Lettuce passwordless autoconfiguration
* add the starter for redis lettuce and ut
  • Loading branch information
saragluna authored May 29, 2024
1 parent ca4d1aa commit b5ffbf3
Show file tree
Hide file tree
Showing 9 changed files with 593 additions and 0 deletions.
5 changes: 5 additions & 0 deletions sdk/boms/spring-cloud-azure-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@
<artifactId>spring-cloud-azure-starter-data-cosmos</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-data-redis-lettuce</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-eventhubs</artifactId>
Expand Down
2 changes: 2 additions & 0 deletions sdk/spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<module>spring-cloud-azure-starter-appconfiguration</module>
<module>spring-cloud-azure-starter-cosmos</module>
<module>spring-cloud-azure-starter-data-cosmos</module>
<module>spring-cloud-azure-starter-data-redis-lettuce</module>
<module>spring-cloud-azure-starter-eventhubs</module>
<module>spring-cloud-azure-starter-eventgrid</module>
<module>spring-cloud-azure-starter-keyvault</module>
Expand Down Expand Up @@ -94,6 +95,7 @@
<module>spring-cloud-azure-starter-appconfiguration</module>
<module>spring-cloud-azure-starter-cosmos</module>
<module>spring-cloud-azure-starter-data-cosmos</module>
<module>spring-cloud-azure-starter-data-redis-lettuce</module>
<module>spring-cloud-azure-starter-eventhubs</module>
<module>spring-cloud-azure-starter-eventgrid</module>
<module>spring-cloud-azure-starter-keyvault</module>
Expand Down
7 changes: 7 additions & 0 deletions sdk/spring/spring-cloud-azure-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@
</dependency>
<!-- Spring Data -->
<!-- Redis -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.3.2.RELEASE</version> <!-- {x-version-update;io.lettuce:lettuce-core;external_dependency} -->
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
Expand Down Expand Up @@ -549,6 +555,7 @@
<include>com.fasterxml.jackson.dataformat:jackson-dataformat-xml:[2.15.4]</include> <!-- {x-include-update;com.fasterxml.jackson.dataformat:jackson-dataformat-xml;external_dependency} -->
<include>com.fasterxml.jackson.datatype:jackson-datatype-jsr310:[2.15.4]</include> <!-- {x-include-update;com.fasterxml.jackson.datatype:jackson-datatype-jsr310;external_dependency} -->
<include>com.fasterxml.jackson.module:jackson-module-afterburner:[2.15.4]</include> <!-- {x-include-update;com.fasterxml.jackson.module:jackson-module-afterburner;external_dependency} -->
<include>io.lettuce:lettuce-core:[6.3.2.RELEASE]</include> <!-- {x-include-update;io.lettuce:lettuce-core;external_dependency} -->
<include>jakarta.servlet:jakarta.servlet-api:[6.0.0]</include> <!-- {x-include-update;jakarta.servlet:jakarta.servlet-api;external_dependency} -->
<include>jakarta.validation:jakarta.validation-api:[3.0.2]</include> <!-- {x-include-update;jakarta.validation:jakarta.validation-api;external_dependency} -->
<include>org.apache.qpid:qpid-jms-client:[2.0.0]</include> <!-- {x-include-update;org.apache.qpid:qpid-jms-client;external_dependency} -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.cloud.autoconfigure.implementation.data.redis;

import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties;
import com.azure.spring.cloud.autoconfigure.implementation.passwordless.properties.AzureRedisPasswordlessProperties;
import com.azure.spring.cloud.autoconfigure.implementation.data.redis.lettuce.AzureRedisCredentials;
import com.azure.spring.cloud.core.implementation.util.AzurePasswordlessPropertiesUtils;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.RedisCredentials;
import io.lettuce.core.RedisCredentialsProvider;
import io.lettuce.core.protocol.ProtocolVersion;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConfiguration;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.connection.lettuce.RedisCredentialsProviderFactory;
import reactor.core.publisher.Mono;


/**
* Azure Redis passwordless connection configuration using Lettuce.
*
* @since 5.13.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({LettuceConnection.class, RedisCredentials.class})
@ConditionalOnExpression("${spring.data.redis.azure.passwordless-enabled:false}")
@AutoConfigureBefore(RedisAutoConfiguration.class)
@ConditionalOnProperty(prefix = "spring.data.redis", name = {"host"})
@EnableConfigurationProperties(RedisProperties.class)
public class AzureLettucePasswordlessAutoConfiguration {

@Bean
@ConfigurationProperties(prefix = "spring.data.redis.azure")
AzureRedisPasswordlessProperties redisPasswordlessProperties() {
return new AzureRedisPasswordlessProperties();
}

@Bean(name = "azureRedisCredentials")
@ConditionalOnMissingBean
AzureRedisCredentials azureRedisCredentials(RedisProperties redisProperties,
AzureRedisPasswordlessProperties azureRedisPasswordlessProperties,
AzureGlobalProperties azureGlobalProperties) {
return new AzureRedisCredentials(redisProperties.getUsername(),
mergeAzureProperties(azureGlobalProperties, azureRedisPasswordlessProperties));
}

@Bean(name = "azureLettuceClientConfigurationBuilderCustomizer")
@ConditionalOnMissingBean
LettuceClientConfigurationBuilderCustomizer azureLettuceClientConfigurationBuilderCustomizer(AzureRedisCredentials azureRedisCredentials) {
return builder -> builder.redisCredentialsProviderFactory(new RedisCredentialsProviderFactory() {

@Override
public RedisCredentialsProvider createCredentialsProvider(RedisConfiguration redisConfiguration) {
return () -> Mono.just(azureRedisCredentials);
}

@Override
public RedisCredentialsProvider createSentinelCredentialsProvider(RedisSentinelConfiguration redisConfiguration) {
return () -> Mono.just(azureRedisCredentials);
}
}).clientOptions(ClientOptions.builder().protocolVersion(ProtocolVersion.RESP2).build());
}

private AzureRedisPasswordlessProperties mergeAzureProperties(AzureGlobalProperties azureGlobalProperties,
AzureRedisPasswordlessProperties azurePasswordlessProperties) {
AzureRedisPasswordlessProperties mergedProperties = new AzureRedisPasswordlessProperties();
AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(azureGlobalProperties, azurePasswordlessProperties, mergedProperties);
return mergedProperties;
}

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

package com.azure.spring.cloud.autoconfigure.implementation.data.redis.lettuce;

import com.azure.core.credential.TokenCredential;
import com.azure.identity.extensions.implementation.template.AzureAuthenticationTemplate;
import com.azure.spring.cloud.core.properties.PasswordlessProperties;
import io.lettuce.core.RedisCredentials;

import java.util.Objects;

public class AzureRedisCredentials implements RedisCredentials {

private final AzureAuthenticationTemplate azureAuthenticationTemplate;
private final String username;

/**
* Create instance of Azure Redis Credentials
* @param username the username to be used for authentication.
*/
public AzureRedisCredentials(String username, PasswordlessProperties passwordlessProperties) {
Objects.requireNonNull(username, "Username is required");
Objects.requireNonNull(passwordlessProperties, "PasswordlessProperties is required");
this.username = username;
azureAuthenticationTemplate = new AzureAuthenticationTemplate();
azureAuthenticationTemplate.init(passwordlessProperties.toPasswordlessProperties());
}

public AzureRedisCredentials(String username, PasswordlessProperties passwordlessProperties, TokenCredential tokenCredential) {
Objects.requireNonNull(username, "Username is required");
Objects.requireNonNull(passwordlessProperties, "PasswordlessProperties is required");
Objects.requireNonNull(tokenCredential, "TokenCredential is required");
this.username = username;
this.azureAuthenticationTemplate = new AzureAuthenticationTemplate(() -> tokenCredential, null);
this.azureAuthenticationTemplate.init(passwordlessProperties.toPasswordlessProperties());
}

@Override
public String getUsername() {
return username;
}

@Override
public boolean hasUsername() {
return username != null;
}

@Override
public char[] getPassword() {
return azureAuthenticationTemplate.getTokenAsPassword().toCharArray();
}

@Override
public boolean hasPassword() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

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

import com.azure.spring.cloud.core.properties.PasswordlessProperties;
import com.azure.spring.cloud.core.properties.authentication.TokenCredentialProperties;
import com.azure.spring.cloud.core.properties.profile.AzureProfileProperties;
import com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider;

import java.util.HashMap;
import java.util.Map;

/**
* Configuration properties for passwordless connections with Azure Redis.
*/
public class AzureRedisPasswordlessProperties implements PasswordlessProperties {

private static final String REDIS_SCOPE_AZURE = "https://redis.azure.com/.default";
private static final String REDIS_SCOPE_AZURE_CHINA = "https://*.cacheinfra.windows.net.china:10225/appid/.default";
private static final String REDIS_SCOPE_AZURE_US_GOVERNMENT = "https://*.cacheinfra.windows.us.government.net:10225/appid/.default";

private static final Map<CloudType, String> REDIS_SCOPE_MAP = new HashMap<CloudType, String>() {
{
put(AzureProfileOptionsProvider.CloudType.AZURE, REDIS_SCOPE_AZURE);
put(AzureProfileOptionsProvider.CloudType.AZURE_CHINA, REDIS_SCOPE_AZURE_CHINA);
put(AzureProfileOptionsProvider.CloudType.AZURE_US_GOVERNMENT, REDIS_SCOPE_AZURE_US_GOVERNMENT);
}
};

private AzureProfileProperties profile = new AzureProfileProperties();

private String scopes;

/**
* Whether to enable supporting azure identity token credentials, by default is false.
*
* If the passwordlessEnabled is true, but the redis password properties is not null, it will still use username/password to authenticate connections.
*/
private boolean passwordlessEnabled = false;

private TokenCredentialProperties credential = new TokenCredentialProperties();

/**
* Get the scopes required for the access token.
*
* @return scopes required for the access token
*/
@Override
public String getScopes() {
return this.scopes == null ? getDefaultScopes() : this.scopes;
}

/**
* Set the scopes required for the access token.
*
* @param scopes the scopes required for the access token
*/
@Override
public void setScopes(String scopes) {
this.scopes = scopes;
}

/**
* Whether to enable connections authenticating with Azure AD, default is false.
*
* @return enable connections authenticating with Azure AD if true, otherwise false.
*/
@Override
public boolean isPasswordlessEnabled() {
return this.passwordlessEnabled;
}

/**
* Set the value to enable/disable connections authenticating with Azure AD.
* If not set, by default the value is false.
*
* @param passwordlessEnabled the passwordlessEnabled
*/
@Override
public void setPasswordlessEnabled(boolean passwordlessEnabled) {
this.passwordlessEnabled = passwordlessEnabled;
}

private String getDefaultScopes() {
return REDIS_SCOPE_MAP.getOrDefault(getProfile().getCloudType(), REDIS_SCOPE_AZURE);
}

/**
* Get the profile
* @return the profile
*/
@Override
public AzureProfileProperties getProfile() {
return profile;
}

/**
* Set the profile
* @param profile the profile properties related to an Azure subscription
*/
public void setProfile(AzureProfileProperties profile) {
this.profile = profile;
}

/**
* Get the credential properties.
*
* @return the credential properties.
*/
@Override
public TokenCredentialProperties getCredential() {
return credential;
}

/**
* Set the credential properties.
*
* @param credential the credential properties
*/
public void setCredential(TokenCredentialProperties credential) {
this.credential = credential;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ com.azure.spring.cloud.autoconfigure.implementation.jdbc.AzureJdbcAutoConfigurat
com.azure.spring.cloud.autoconfigure.implementation.data.cosmos.CosmosDataAutoConfiguration
com.azure.spring.cloud.autoconfigure.implementation.data.cosmos.CosmosRepositoriesAutoConfiguration
com.azure.spring.cloud.autoconfigure.implementation.data.cosmos.CosmosReactiveRepositoriesAutoConfiguration
com.azure.spring.cloud.autoconfigure.implementation.data.redis.AzureLettucePasswordlessAutoConfiguration
Loading

0 comments on commit b5ffbf3

Please sign in to comment.