11package io .lettuce .authx ;
22
3- import io .lettuce .core .ClientOptions ;
4- import io .lettuce .core .RedisClient ;
5- import io .lettuce .core .RedisFuture ;
6- import io .lettuce .core .RedisURI ;
7- import io .lettuce .core .SocketOptions ;
8- import io .lettuce .core .TimeoutOptions ;
9- import io .lettuce .core .TransactionResult ;
3+ import com .azure .identity .DefaultAzureCredential ;
4+ import com .azure .identity .DefaultAzureCredentialBuilder ;
5+ import io .lettuce .core .*;
106import io .lettuce .core .api .StatefulRedisConnection ;
117import io .lettuce .core .api .async .RedisAsyncCommands ;
128import io .lettuce .core .api .sync .RedisCommands ;
13- import io .lettuce .core .cluster .ClusterClientOptions ;
149import io .lettuce .core .pubsub .StatefulRedisPubSubConnection ;
1510import io .lettuce .core .support .PubSubTestListener ;
1611import io .lettuce .test .Wait ;
1712import io .lettuce .test .env .Endpoints ;
1813import io .lettuce .test .env .Endpoints .Endpoint ;
19- import org .junit .jupiter .api .AfterAll ;
20- import org .junit .jupiter .api .Assumptions ;
21- import org .junit .jupiter .api .BeforeAll ;
22- import org .junit .jupiter .api .Tag ;
23- import org .junit .jupiter .api .Test ;
14+ import org .junit .jupiter .api .*;
2415import redis .clients .authentication .core .TokenAuthConfig ;
16+ import redis .clients .authentication .entraid .AzureTokenAuthConfigBuilder ;
2517import redis .clients .authentication .entraid .EntraIDTokenAuthConfigBuilder ;
2618
2719import java .time .Duration ;
@@ -41,52 +33,49 @@ public class EntraIdIntegrationTests {
4133
4234 private static final EntraIdTestContext testCtx = EntraIdTestContext .DEFAULT ;
4335
44- private static TokenBasedRedisCredentialsProvider credentialsProvider ;
36+ private RedisClient client ;
4537
46- private static RedisClient client ;
38+ private Endpoint standalone ;
4739
48- private static Endpoint standalone ;
40+ private ClientOptions clientOptions ;
4941
50- @ BeforeAll
51- public static void setup () {
42+ private TokenBasedRedisCredentialsProvider credentialsProvider ;
43+
44+ @ BeforeEach
45+ public void setup () {
5246 standalone = Endpoints .DEFAULT .getEndpoint ("standalone-entraid-acl" );
53- if (standalone != null ) {
54- Assumptions .assumeTrue (testCtx .getClientId () != null && testCtx .getClientSecret () != null ,
55- "Skipping EntraID tests. Azure AD credentials not provided!" );
56- // Configure timeout options to assure fast test failover
57- ClusterClientOptions clientOptions = ClusterClientOptions .builder ()
58- .socketOptions (SocketOptions .builder ().connectTimeout (Duration .ofSeconds (1 )).build ())
59- .timeoutOptions (TimeoutOptions .enabled (Duration .ofSeconds (1 )))
60- // enable re-authentication
61- .reauthenticateBehavior (ClientOptions .ReauthenticateBehavior .ON_NEW_CREDENTIALS ).build ();
62-
63- TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder .builder ().clientId (testCtx .getClientId ())
64- .secret (testCtx .getClientSecret ()).authority (testCtx .getAuthority ()).scopes (testCtx .getRedisScopes ())
65- .expirationRefreshRatio (0.0000001F ).build ();
66-
67- credentialsProvider = TokenBasedRedisCredentialsProvider .create (tokenAuthConfig );
68-
69- RedisURI uri = RedisURI .create ((standalone .getEndpoints ().get (0 )));
70- uri .setCredentialsProvider (credentialsProvider );
71- client = RedisClient .create (uri );
72- client .setOptions (clientOptions );
47+ assumeTrue (standalone != null , "Skipping EntraID tests. Redis host with enabled EntraId not provided!" );
48+ Assumptions .assumeTrue (testCtx .getClientId () != null && testCtx .getClientSecret () != null ,
49+ "Skipping EntraID tests. Azure AD credentials not provided!" );
7350
74- }
51+ clientOptions = ClientOptions .builder ()
52+ .socketOptions (SocketOptions .builder ().connectTimeout (Duration .ofSeconds (1 )).build ())
53+ .timeoutOptions (TimeoutOptions .enabled (Duration .ofSeconds (1 )))
54+ .reauthenticateBehavior (ClientOptions .ReauthenticateBehavior .ON_NEW_CREDENTIALS ).build ();
55+
56+ TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder .builder ().clientId (testCtx .getClientId ())
57+ .secret (testCtx .getClientSecret ()).authority (testCtx .getAuthority ()).scopes (testCtx .getRedisScopes ())
58+ .expirationRefreshRatio (0.0000001F ).build ();
59+
60+ TokenBasedRedisCredentialsProvider credentialsProvider = TokenBasedRedisCredentialsProvider .create (tokenAuthConfig );
61+
62+ client = createClient (credentialsProvider );
7563 }
7664
77- @ AfterAll
78- public static void cleanup () {
65+ @ AfterEach
66+ public void cleanUp () {
7967 if (credentialsProvider != null ) {
8068 credentialsProvider .close ();
8169 }
70+ if (client != null ) {
71+ client .shutdown ();
72+ }
8273 }
8374
8475 // T.1.1
8576 // Verify authentication using Azure AD with service principals using Redis Standalone client
8677 @ Test
8778 public void standaloneWithSecret_azureServicePrincipalIntegrationTest () throws ExecutionException , InterruptedException {
88- assumeTrue (standalone != null , "Skipping EntraID tests. Redis host with enabled EntraId not provided!" );
89-
9079 try (StatefulRedisConnection <String , String > connection = client .connect ()) {
9180 RedisCommands <String , String > sync = connection .sync ();
9281 String key = UUID .randomUUID ().toString ();
@@ -102,32 +91,23 @@ public void standaloneWithSecret_azureServicePrincipalIntegrationTest() throws E
10291 // Test that the Redis client is not blocked/interrupted during token renewal.
10392 @ Test
10493 public void renewalDuringOperationsTest () throws InterruptedException {
105- assumeTrue (standalone != null , "Skipping EntraID tests. Redis host with enabled EntraId not provided!" );
106-
107- // Counter to track the number of command cycles
10894 AtomicInteger commandCycleCount = new AtomicInteger (0 );
10995
110- // Start a thread to continuously send Redis commands
11196 Thread commandThread = new Thread (() -> {
11297 try (StatefulRedisConnection <String , String > connection = client .connect ()) {
11398 RedisAsyncCommands <String , String > async = connection .async ();
11499 for (int i = 1 ; i <= 10 ; i ++) {
115- // Start a transaction with SET and INCRBY commands
116- RedisFuture <String > multi = async .multi ();
117- RedisFuture <String > set = async .set ("key" , "1" );
118- RedisFuture <Long > incrby = async .incrby ("key" , 1 );
100+ async .multi ();
101+ async .set ("key" , "1" );
102+ async .incrby ("key" , 1 );
119103 RedisFuture <TransactionResult > exec = async .exec ();
120104 TransactionResult results = exec .get (1 , TimeUnit .SECONDS );
121105
122- // Increment the command cycle count after each execution
123106 commandCycleCount .incrementAndGet ();
124107
125- // Verify the results from EXEC
126- assertThat (results ).hasSize (2 ); // We expect 2 responses: SET and INCRBY
127-
128- // Check the response from each command in the transaction
129- assertThat ((String ) results .get (0 )).isEqualTo ("OK" ); // SET "key" = "1"
130- assertThat ((Long ) results .get (1 )).isEqualTo (2L ); // INCRBY "key" by 1, expected result is 2
108+ assertThat (results ).hasSize (2 );
109+ assertThat ((String ) results .get (0 )).isEqualTo ("OK" );
110+ assertThat ((Long ) results .get (1 )).isEqualTo (2L );
131111 }
132112 } catch (Exception e ) {
133113 fail ("Command execution failed during token refresh" , e );
@@ -136,16 +116,12 @@ public void renewalDuringOperationsTest() throws InterruptedException {
136116
137117 commandThread .start ();
138118
139- CountDownLatch latch = new CountDownLatch (10 ); // Wait for at least 10 token renewals
140-
141- credentialsProvider .credentials ().subscribe (cred -> {
142- latch .countDown (); // Signal each renewal as it's received
143- });
119+ CountDownLatch latch = new CountDownLatch (10 ); // Wait for at least 10 token renewalss
120+ credentialsProvider .credentials ().subscribe (cred -> latch .countDown ());
144121
145122 assertThat (latch .await (1 , TimeUnit .SECONDS )).isTrue (); // Wait to reach 10 renewals
146123 commandThread .join (); // Wait for the command thread to finish
147124
148- // Verify that at least 10 command cycles were executed during the test
149125 assertThat (commandCycleCount .get ()).isGreaterThanOrEqualTo (10 );
150126 }
151127
@@ -162,7 +138,6 @@ public void renewalDuringPubSubOperationsTest() throws InterruptedException {
162138 connectionPubSub .addListener (listener );
163139 connectionPubSub .sync ().subscribe ("channel" );
164140
165- // Start a thread to continuously send Redis commands
166141 Thread pubsubThread = new Thread (() -> {
167142 for (int i = 1 ; i <= 100 ; i ++) {
168143 connectionPubSub1 .sync ().publish ("channel" , "message" );
@@ -172,17 +147,48 @@ public void renewalDuringPubSubOperationsTest() throws InterruptedException {
172147 pubsubThread .start ();
173148
174149 CountDownLatch latch = new CountDownLatch (10 );
175- credentialsProvider .credentials ().subscribe (cred -> {
176- latch .countDown ();
177- });
150+ credentialsProvider .credentials ().subscribe (cred -> latch .countDown ());
178151
179152 assertThat (latch .await (2 , TimeUnit .SECONDS )).isTrue (); // Wait for at least 10 token renewals
180153 pubsubThread .join (); // Wait for the pub/sub thread to finish
181154
182- // Verify that all messages were received
183155 Wait .untilEquals (100 , () -> listener .getMessages ().size ()).waitOrTimeout ();
184156 assertThat (listener .getMessages ()).allMatch (msg -> msg .equals ("message" ));
185157 }
186158 }
187159
160+ @ Test
161+ public void azureTokenAuthWithDefaultAzureCredentials () throws ExecutionException , InterruptedException {
162+ DefaultAzureCredential credential = new DefaultAzureCredentialBuilder ().build ();
163+
164+ TokenAuthConfig tokenAuthConfig = AzureTokenAuthConfigBuilder .builder ().defaultAzureCredential (credential )
165+ .tokenRequestExecTimeoutInMs (2000 ).build ();
166+
167+ try (RedisClient azureCredClient = createClient (credentialsProvider );
168+ TokenBasedRedisCredentialsProvider credentialsProvider = TokenBasedRedisCredentialsProvider
169+ .create (tokenAuthConfig );) {
170+ RedisCredentials credentials = credentialsProvider .resolveCredentials ().block (Duration .ofSeconds (5 ));
171+ assertThat (credentials ).isNotNull ();
172+
173+ String key = UUID .randomUUID ().toString ();
174+ try (StatefulRedisConnection <String , String > connection = azureCredClient .connect ()) {
175+ RedisCommands <String , String > sync = connection .sync ();
176+ assertThat (sync .aclWhoami ()).isEqualTo (credentials .getUsername ());
177+ sync .set (key , "value" );
178+ assertThat (sync .get (key )).isEqualTo ("value" );
179+ assertThat (connection .async ().get (key ).get ()).isEqualTo ("value" );
180+ assertThat (connection .reactive ().get (key ).block ()).isEqualTo ("value" );
181+ sync .del (key );
182+ }
183+ }
184+ }
185+
186+ private RedisClient createClient (TokenBasedRedisCredentialsProvider credentialsProvider ) {
187+ RedisURI uri = RedisURI .create ((standalone .getEndpoints ().get (0 )));
188+ uri .setCredentialsProvider (credentialsProvider );
189+ RedisClient redis = RedisClient .create (uri );
190+ redis .setOptions (clientOptions );
191+ return redis ;
192+ }
193+
188194}
0 commit comments