Skip to content

Commit 077e460

Browse files
authored
Add support for DefaultAzureCredentials (#14)
* add support for DefaultAzureCredentials * set default for tokenRequestExecTimeoutInMs * add AZURE_TENANT_ID to env * adding java docs * fix javadoc
1 parent 1afe57a commit 077e460

File tree

12 files changed

+457
-10
lines changed

12 files changed

+457
-10
lines changed

.github/workflows/entraid_integration.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,4 @@ jobs:
6464
AZURE_CERT: ${{secrets.AZURE_CERT}}
6565
AZURE_PRIVATE_KEY: ${{secrets.AZURE_PRIVATE_KEY}}
6666
AZURE_REDIS_SCOPES: ${{secrets.AZURE_REDIS_SCOPES}}
67+
AZURE_TENANT_ID: ${{secrets.AZURE_TENANT_ID}}

core/src/main/java/redis/clients/authentication/core/Dispatcher.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
/*
2-
* Copyright 2024, Redis Ltd. and Contributors All rights reserved. Licensed under the MIT License.
2+
* Copyright 2024, Redis Ltd. and Contributors
3+
* All rights reserved.
4+
*
5+
* Licensed under the MIT License.
36
*/
47
package redis.clients.authentication.core;
58

core/src/main/java/redis/clients/authentication/core/RenewalScheduler.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
/*
2-
* Copyright 2024, Redis Ltd. and Contributors All rights reserved. Licensed under the MIT License.
2+
* Copyright 2024, Redis Ltd. and Contributors
3+
* All rights reserved.
4+
*
5+
* Licensed under the MIT License.
36
*/
47
package redis.clients.authentication.core;
58

core/src/main/java/redis/clients/authentication/core/TokenManager.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
/*
2-
* Copyright 2024, Redis Ltd. and Contributors All rights reserved. Licensed under the MIT License.
2+
* Copyright 2024, Redis Ltd. and Contributors
3+
* All rights reserved.
4+
*
5+
* Licensed under the MIT License.
36
*/
47
package redis.clients.authentication.core;
58

entraid/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
<artifactId>msal4j</artifactId>
6868
<version>1.17.2</version>
6969
</dependency>
70+
<dependency>
71+
<groupId>com.azure</groupId>
72+
<artifactId>azure-identity</artifactId>
73+
<version>1.15.3</version>
74+
</dependency>
7075
<dependency>
7176
<groupId>junit</groupId>
7277
<artifactId>junit</artifactId>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2024, Redis Ltd. and Contributors
3+
* All rights reserved.
4+
*
5+
* Licensed under the MIT License.
6+
*/
7+
package redis.clients.authentication.entraid;
8+
9+
import java.time.Duration;
10+
import java.util.ArrayList;
11+
import java.util.Set;
12+
import java.util.function.Supplier;
13+
14+
import com.azure.core.credential.AccessToken;
15+
import com.azure.core.credential.TokenRequestContext;
16+
import com.azure.identity.DefaultAzureCredential;
17+
import redis.clients.authentication.core.IdentityProvider;
18+
import redis.clients.authentication.core.Token;
19+
20+
/**
21+
* AzureIdentityProvider is an implementation of the IdentityProvider interface
22+
* that uses Azure's DefaultAzureCredential to obtain access tokens.
23+
*
24+
* <p>This class is designed to work with Azure's identity platform to provide
25+
* authentication tokens for accessing Azure resources. It uses a
26+
* DefaultAzureCredential to request tokens with specified scopes and a timeout(in milliseconds).
27+
* For most cases you will not need to use it directly since AzureTokenAuthConfigBuilder
28+
* will do the work for you as shown in the example below:
29+
* <pre>
30+
* {@code
31+
* TokenAuthConfig config = AzureTokenAuthConfigBuilder.builder()
32+
* .defaultAzureCredential(new DefaultAzureCredential()).build();
33+
* }
34+
* </pre>
35+
* <p>In you case you need your own implementation for relevant reasons, you can use it as follows:
36+
* <pre>
37+
* {@code
38+
* Set<String> scopes = new HashSet<>(Arrays.asList("https://redis.azure.com/.default"));
39+
* AzureIdentityProvider provider = new AzureIdentityProvider(
40+
* new DefaultAzureCredentialBuilder().build(), scopes, 5000);
41+
* TokenAuthConfig config = AzureTokenAuthConfigBuilder.builder().identityProviderConfig(()-> provider)).build();
42+
* }
43+
* </pre>
44+
*
45+
* <p>Thread Safety: This class is thread-safe as long as the provided
46+
* DefaultAzureCredential is thread-safe.
47+
*
48+
* @see redis.clients.authentication.entraid.AzureTokenAuthConfigBuilder
49+
* @see com.azure.identity.DefaultAzureCredentialBuilder
50+
*/
51+
52+
public final class AzureIdentityProvider implements IdentityProvider {
53+
54+
private Supplier<AccessToken> accessTokenSupplier;
55+
56+
public AzureIdentityProvider(DefaultAzureCredential defaultAzureCredential, Set<String> scopes,
57+
int timeout) {
58+
TokenRequestContext ctx = new TokenRequestContext()
59+
.setScopes(new ArrayList<String>(scopes));
60+
accessTokenSupplier = () -> defaultAzureCredential.getToken(ctx)
61+
.block(Duration.ofMillis(timeout));
62+
}
63+
64+
@Override
65+
public Token requestToken() {
66+
return new JWToken(accessTokenSupplier.get().getToken());
67+
}
68+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2024, Redis Ltd. and Contributors
3+
* All rights reserved.
4+
*
5+
* Licensed under the MIT License.
6+
*/
7+
package redis.clients.authentication.entraid;
8+
9+
import java.util.Set;
10+
import java.util.function.Supplier;
11+
12+
import com.azure.identity.DefaultAzureCredential;
13+
14+
import redis.clients.authentication.core.IdentityProvider;
15+
import redis.clients.authentication.core.IdentityProviderConfig;
16+
17+
/**
18+
* Configuration class for Azure Identity Provider.
19+
* This class implements the {@link IdentityProviderConfig} interface and provides
20+
* a configuration for creating an {@link AzureIdentityProvider} instance.
21+
*
22+
* <p>This class uses {@link DefaultAzureCredential} for authentication and allows
23+
* specifying scopes and a timeout(in milliseconds) for the identity provider.</p>
24+
* For most cases you will not need to use it directly since AzureTokenAuthConfigBuilder
25+
* will do the work for you as shown in the example below:
26+
* <pre>
27+
* {@code
28+
* TokenAuthConfig config = AzureTokenAuthConfigBuilder.builder()
29+
* .defaultAzureCredential(new DefaultAzureCredentialBuilder()).build();
30+
* }
31+
* </pre>
32+
* <p>In you case you need your own implementation for relevant reasons, you can use it as follows:
33+
* <pre>
34+
* {@code
35+
* DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
36+
* Set<String> scopes = Set.of("https://redis.azure.com/.default");
37+
* AzureIdentityProviderConfig azureIDPConfig = new AzureIdentityProviderConfig(credential, scopes, 5000);
38+
* TokenAuthConfig config = AzureTokenAuthConfigBuilder.builder().identityProviderConfig(azureIDPConfig).build();
39+
* }
40+
* </pre>
41+
*
42+
* For more information and details on how to use, please see:
43+
* https://github.com/redis/jedis/blob/master/docs/advanced-usage.md#token-based-authentication
44+
* https://github.com/redis/lettuce/blob/main/docs/user-guide/connecting-redis.md#microsoft-entra-id-authentication
45+
*
46+
* @see IdentityProviderConfig
47+
* @see AzureIdentityProvider
48+
* @see DefaultAzureCredential
49+
*/
50+
public final class AzureIdentityProviderConfig implements IdentityProviderConfig {
51+
52+
private final Supplier<IdentityProvider> providerSupplier;
53+
54+
public AzureIdentityProviderConfig(DefaultAzureCredential defaultAzureCredential, Set<String> scopes, int timeout) {
55+
providerSupplier = () -> new AzureIdentityProvider(defaultAzureCredential, scopes, timeout);
56+
}
57+
58+
@Override
59+
public IdentityProvider getProvider() {
60+
return providerSupplier.get();
61+
}
62+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2024, Redis Ltd. and Contributors
3+
* All rights reserved.
4+
*
5+
* Licensed under the MIT License.
6+
*/
7+
package redis.clients.authentication.entraid;
8+
9+
import java.util.Collections;
10+
import java.util.Set;
11+
12+
import com.azure.identity.DefaultAzureCredential;
13+
14+
import redis.clients.authentication.core.TokenAuthConfig;
15+
import redis.clients.authentication.core.TokenManagerConfig;
16+
17+
/**
18+
* Builder class for configuring Azure Token Authentication via a DefaultAzureCredential.
19+
* It builds a TokenAuthConfig object which can be used to authenticate with Azure resources.
20+
* This class extends {@link TokenAuthConfig.Builder} and implements {@link AutoCloseable}.
21+
* It provides methods to configure various parameters for Azure Token Authentication.
22+
*
23+
* <p>Default values:</p>
24+
* <ul>
25+
* <li>{@code DEFAULT_EXPIRATION_REFRESH_RATIO}: 0.75F</li>
26+
* <li>{@code DEFAULT_LOWER_REFRESH_BOUND_MILLIS}: 2 * 60 * 1000</li>
27+
* <li>{@code DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_IN_MS}: 1000</li>
28+
* <li>{@code DEFAULT_MAX_ATTEMPTS_TO_RETRY}: 5</li>
29+
* <li>{@code DEFAULT_DELAY_IN_MS_TO_RETRY}: 100</li>
30+
* <li>{@code DEFAULT_SCOPES}: "https://redis.azure.com/.default"</li>
31+
* </ul>
32+
*
33+
* <p>Example usage:</p>
34+
* <pre>{@code
35+
* AzureTokenAuthConfigBuilder builder = AzureTokenAuthConfigBuilder.builder()
36+
* .defaultAzureCredential(new DefaultAzureCredentialBuilder.build())
37+
* .scopes(Collections.singleton("https://example.com/.default"))
38+
* .tokenRequestExecTimeoutInMs(2000);
39+
* TokenAuthConfig config = builder.build();
40+
* }</pre>
41+
*
42+
* <p>This class is also {@link AutoCloseable}, and resources can be cleaned
43+
* up by calling {@link #close()}.</p>
44+
*
45+
* @see TokenAuthConfig.Builder
46+
* @see DefaultAzureCredential
47+
*/
48+
public class AzureTokenAuthConfigBuilder
49+
extends TokenAuthConfig.Builder<AzureTokenAuthConfigBuilder> implements AutoCloseable {
50+
public static final float DEFAULT_EXPIRATION_REFRESH_RATIO = 0.75F;
51+
public static final int DEFAULT_LOWER_REFRESH_BOUND_MILLIS = 2 * 60 * 1000;
52+
public static final int DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_IN_MS = 1000;
53+
public static final int DEFAULT_MAX_ATTEMPTS_TO_RETRY = 5;
54+
public static final int DEFAULT_DELAY_IN_MS_TO_RETRY = 100;
55+
public static final Set<String> DEFAULT_SCOPES = Collections.singleton("https://redis.azure.com/.default");;
56+
57+
private DefaultAzureCredential defaultAzureCredential;
58+
private Set<String> scopes = DEFAULT_SCOPES;
59+
private int tokenRequestExecTimeoutInMs = DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_IN_MS;
60+
61+
public AzureTokenAuthConfigBuilder() {
62+
this.expirationRefreshRatio(DEFAULT_EXPIRATION_REFRESH_RATIO)
63+
.lowerRefreshBoundMillis(DEFAULT_LOWER_REFRESH_BOUND_MILLIS)
64+
.tokenRequestExecTimeoutInMs(DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_IN_MS)
65+
.maxAttemptsToRetry(DEFAULT_MAX_ATTEMPTS_TO_RETRY)
66+
.delayInMsToRetry(DEFAULT_DELAY_IN_MS_TO_RETRY);
67+
}
68+
69+
public AzureTokenAuthConfigBuilder defaultAzureCredential(
70+
DefaultAzureCredential defaultAzureCredential) {
71+
this.defaultAzureCredential = defaultAzureCredential;
72+
return this;
73+
}
74+
75+
public AzureTokenAuthConfigBuilder scopes(Set<String> scopes) {
76+
this.scopes = scopes;
77+
return this;
78+
}
79+
80+
@Override
81+
public AzureTokenAuthConfigBuilder tokenRequestExecTimeoutInMs(
82+
int tokenRequestExecTimeoutInMs) {
83+
super.tokenRequestExecTimeoutInMs(tokenRequestExecTimeoutInMs);
84+
this.tokenRequestExecTimeoutInMs = tokenRequestExecTimeoutInMs;
85+
return this;
86+
}
87+
88+
public TokenAuthConfig build() {
89+
super.identityProviderConfig(new AzureIdentityProviderConfig(defaultAzureCredential, scopes,
90+
tokenRequestExecTimeoutInMs));
91+
return super.build();
92+
}
93+
94+
@Override
95+
public void close() throws Exception {
96+
defaultAzureCredential = null;
97+
scopes = null;
98+
}
99+
100+
public static AzureTokenAuthConfigBuilder builder() {
101+
return new AzureTokenAuthConfigBuilder();
102+
}
103+
104+
public static AzureTokenAuthConfigBuilder from(AzureTokenAuthConfigBuilder sample) {
105+
TokenAuthConfig tokenAuthConfig = TokenAuthConfig.Builder.from(sample).build();
106+
TokenManagerConfig tokenManagerConfig = tokenAuthConfig.getTokenManagerConfig();
107+
108+
AzureTokenAuthConfigBuilder builder = (AzureTokenAuthConfigBuilder) new AzureTokenAuthConfigBuilder()
109+
.expirationRefreshRatio(tokenManagerConfig.getExpirationRefreshRatio())
110+
.lowerRefreshBoundMillis(tokenManagerConfig.getLowerRefreshBoundMillis())
111+
.tokenRequestExecTimeoutInMs(tokenManagerConfig.getTokenRequestExecTimeoutInMs())
112+
.maxAttemptsToRetry(tokenManagerConfig.getRetryPolicy().getMaxAttempts())
113+
.delayInMsToRetry(tokenManagerConfig.getRetryPolicy().getdelayInMs())
114+
.identityProviderConfig(tokenAuthConfig.getIdentityProviderConfig());
115+
builder.scopes = sample.scopes;
116+
return builder;
117+
}
118+
}

entraid/src/main/java/redis/clients/authentication/entraid/EntraIDTokenAuthConfigBuilder.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,60 @@
1818
import redis.clients.authentication.entraid.ManagedIdentityInfo.UserManagedIdentityType;
1919
import redis.clients.authentication.entraid.ServicePrincipalInfo.ServicePrincipalAccess;
2020

21+
/**
22+
* Builder class for configuring EntraID token authentication.
23+
* This class provides methods to set various configuration options for EntraID token authentication.
24+
* It extends the TokenAuthConfig.Builder class and implements AutoCloseable.
25+
*
26+
* <p>Default values:</p>
27+
* <ul>
28+
* <li>DEFAULT_EXPIRATION_REFRESH_RATIO: 0.75F</li>
29+
* <li>DEFAULT_LOWER_REFRESH_BOUND_MILLIS: 120000 (2 minutes)</li>
30+
* <li>DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_IN_MS: 1000 (1 second)</li>
31+
* <li>DEFAULT_MAX_ATTEMPTS_TO_RETRY: 5</li>
32+
* <li>DEFAULT_DELAY_IN_MS_TO_RETRY: 100 (0.1 second)</li>
33+
* </ul>
34+
*
35+
* <p>Configuration options:</p>
36+
* <ul>
37+
* <li>{@link #clientId(String)}: Sets the client ID.</li>
38+
* <li>{@link #secret(String)}: Sets the client secret and configures access with secret.</li>
39+
* <li>{@link #key(PrivateKey, X509Certificate)}: Sets the private key and certificate,
40+
* and configures access with certificate.</li>
41+
* <li>{@link #authority(String)}: Sets the authority URL.</li>
42+
* <li>{@link #systemAssignedManagedIdentity()}: Configures system-assigned managed identity.</li>
43+
* <li>{@link #userAssignedManagedIdentity(UserManagedIdentityType, String)}:
44+
* Configures user-assigned managed identity.</li>
45+
* <li>{@link #customEntraIdAuthenticationSupplier(Supplier)}: Sets a custom authentication supplier.</li>
46+
* <li>{@link #scopes(Set)}: Sets the scopes for the token request.</li>
47+
* <li>{@link #tokenRequestExecTimeoutInMs(int)}: Sets the token request execution timeout in milliseconds.</li>
48+
* </ul>
49+
*
50+
* <p>Usage:</p>
51+
* <pre>
52+
* {@code
53+
* EntraIDTokenAuthConfigBuilder builder = EntraIDTokenAuthConfigBuilder.builder()
54+
* .clientId("your-client-id")
55+
* .secret("your-secret")
56+
* .authority("https://login.microsoftonline.com/your-tenant-id")
57+
* .scopes(Set.of("https://redis.azure.com/.default"))
58+
* .build();
59+
* }
60+
* </pre>
61+
*
62+
* <p>Note:</p>
63+
* <ul>
64+
* <li>Only one of ServicePrincipal, ManagedIdentity or customEntraIdAuthenticationSupplier can be configured.</li>
65+
* <li>Throws RedisEntraIDException if conflicting configurations are provided.</li>
66+
* </ul>
67+
* For more information and details on how to use, please see:
68+
* <p>https://github.com/redis/jedis/blob/master/docs/advanced-usage.md#token-based-authentication
69+
* <p>https://github.com/redis/lettuce/blob/main/docs/user-guide/connecting-redis.md#microsoft-entra-id-authentication
70+
*
71+
* @see TokenAuthConfig.Builder
72+
* @see AutoCloseable
73+
*
74+
*/
2175
public class EntraIDTokenAuthConfigBuilder
2276
extends TokenAuthConfig.Builder<EntraIDTokenAuthConfigBuilder> implements AutoCloseable {
2377
public static final float DEFAULT_EXPIRATION_REFRESH_RATIO = 0.75F;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2024, Redis Ltd. and Contributors
3+
* All rights reserved.
4+
*
5+
* Licensed under the MIT License.
6+
*/
7+
package redis.clients.authentication;
8+
9+
import static org.junit.Assert.assertFalse;
10+
import static org.junit.Assert.assertNotNull;
11+
import org.junit.Test;
12+
import com.azure.identity.DefaultAzureCredential;
13+
import com.azure.identity.DefaultAzureCredentialBuilder;
14+
15+
import redis.clients.authentication.core.Token;
16+
import redis.clients.authentication.entraid.AzureIdentityProvider;
17+
import redis.clients.authentication.entraid.AzureTokenAuthConfigBuilder;
18+
19+
public class AzureIdentityProviderIntegrationTests {
20+
21+
@Test
22+
public void requestTokenWithDefaultAzureCredential() {
23+
// ensure environment variables are set
24+
String client_id = System.getenv(TestContext.AZURE_CLIENT_ID);
25+
assertNotNull(client_id);
26+
assertFalse(client_id.isEmpty());
27+
String clientSecret = System.getenv(TestContext.AZURE_CLIENT_SECRET);
28+
assertNotNull(clientSecret);
29+
assertFalse(clientSecret.isEmpty());
30+
String tenantId = System.getenv("AZURE_TENANT_ID");
31+
assertNotNull(tenantId);
32+
assertFalse(tenantId.isEmpty());
33+
34+
DefaultAzureCredential defaultAzureCredential = new DefaultAzureCredentialBuilder().build();
35+
Token token = new AzureIdentityProvider(defaultAzureCredential,
36+
AzureTokenAuthConfigBuilder.DEFAULT_SCOPES, 2000).requestToken();
37+
assertNotNull(token.getValue());
38+
}
39+
}

0 commit comments

Comments
 (0)