Skip to content

Commit 78fb436

Browse files
committed
Rebase on PR #3068
# Conflicts: # src/test/java/io/lettuce/core/AuthenticationIntegrationTests.java
1 parent cdb12a1 commit 78fb436

File tree

4 files changed

+86
-58
lines changed

4 files changed

+86
-58
lines changed

src/main/java/io/lettuce/authx/TokenBasedRedisCredentialsProvider.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,14 @@ private void initializeTokenManager() {
3535

3636
@Override
3737
public void onTokenRenewed(Token token) {
38-
String username = token.tryGet("oid");
39-
char[] pass = token.getValue().toCharArray();
40-
RedisCredentials credentials = RedisCredentials.just(username, pass);
41-
credentialsSink.tryEmitNext(credentials);
38+
try {
39+
String username = token.tryGet("oid");
40+
char[] pass = token.getValue().toCharArray();
41+
RedisCredentials credentials = RedisCredentials.just(username, pass);
42+
credentialsSink.tryEmitNext(credentials);
43+
} catch (Exception e) {
44+
credentialsSink.emitError(e, Sinks.EmitFailureHandler.FAIL_FAST);
45+
}
4246
}
4347

4448
@Override

src/test/java/io/lettuce/core/AuthenticationIntegrationTests.java

Lines changed: 44 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55

66
import javax.inject.Inject;
77

8-
import io.lettuce.authx.TokenBasedRedisCredentialsProvider;
98
import io.lettuce.authx.TokenBasedRedisCredentialsProvider;
109
import io.lettuce.core.event.command.CommandListener;
1110
import io.lettuce.core.event.command.CommandSucceededEvent;
1211
import io.lettuce.core.protocol.RedisCommand;
13-
import io.lettuce.test.Delay;
14-
import io.lettuce.test.Delay;
1512
import org.awaitility.Awaitility;
1613
import org.junit.jupiter.api.BeforeEach;
1714
import org.junit.jupiter.api.Tag;
@@ -27,11 +24,13 @@
2724
import io.lettuce.test.WithPassword;
2825
import io.lettuce.test.condition.EnabledOnCommand;
2926
import io.lettuce.test.settings.TestSettings;
30-
import reactor.core.publisher.Flux;
3127
import reactor.core.publisher.Mono;
28+
import redis.clients.authentication.core.SimpleToken;
3229

3330
import java.time.Duration;
31+
import java.time.Instant;
3432
import java.util.ArrayList;
33+
import java.util.Collections;
3534
import java.util.List;
3635

3736
/**
@@ -126,6 +125,43 @@ void streamingCredentialProvider(RedisClient client) {
126125
client.getOptions().mutate().reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.DEFAULT).build());
127126
}
128127

128+
@Test
129+
@Inject
130+
void tokenBasedCredentialProvider(RedisClient client) {
131+
132+
TestCommandListener listener = new TestCommandListener();
133+
client.addListener(listener);
134+
client.setOptions(client.getOptions().mutate()
135+
.reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build());
136+
137+
TestTokenManager tokenManager = new TestTokenManager(null, null);
138+
TokenBasedRedisCredentialsProvider credentialsProvider = new TokenBasedRedisCredentialsProvider(tokenManager);
139+
140+
// Build RedisURI with streaming credentials provider
141+
RedisURI uri = RedisURI.builder().withHost(TestSettings.host()).withPort(TestSettings.port())
142+
.withClientName("streaming_cred_test").withAuthentication(credentialsProvider)
143+
.withTimeout(Duration.ofSeconds(5)).build();
144+
tokenManager.emitToken(testToken(TestSettings.username(), TestSettings.password().toString().toCharArray()));
145+
146+
StatefulRedisConnection<String, String> connection = client.connect(StringCodec.UTF8, uri);
147+
assertThat(connection.sync().aclWhoami()).isEqualTo(TestSettings.username());
148+
149+
// rotate the credentials
150+
tokenManager.emitToken(testToken("steave", "foobared".toCharArray()));
151+
152+
Awaitility.await().atMost(Duration.ofSeconds(1)).until(() -> listener.succeeded.stream()
153+
.anyMatch(command -> isAuthCommandWithCredentials(command, "steave", "foobared".toCharArray())));
154+
155+
// verify that the connection is re-authenticated with the new user credentials
156+
assertThat(connection.sync().aclWhoami()).isEqualTo("steave");
157+
158+
credentialsProvider.shutdown();
159+
connection.close();
160+
client.removeListener(listener);
161+
client.setOptions(
162+
client.getOptions().mutate().reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.DEFAULT).build());
163+
}
164+
129165
static class TestCommandListener implements CommandListener {
130166

131167
final List<RedisCommand<?, ?, ?>> succeeded = new ArrayList<>();
@@ -147,52 +183,9 @@ private boolean isAuthCommandWithCredentials(RedisCommand<?, ?, ?> command, Stri
147183
return false;
148184
}
149185

150-
}
151-
152-
@Test
153-
@Inject
154-
void tokenBasedCredentialProvider(RedisClient client) {
155-
156-
ClientOptions clientOptions = ClientOptions.builder()
157-
.disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS).build();
158-
client.setOptions(clientOptions);
159-
// Connection used to simulate test user credential rotation
160-
StatefulRedisConnection<String, String> defaultConnection = client.connect();
161-
162-
String testUser = "streaming_cred_test_user";
163-
char[] testPassword1 = "token_1".toCharArray();
164-
char[] testPassword2 = "token_2".toCharArray();
165-
166-
TestTokenManager tokenManager = new TestTokenManager(null, null);
167-
168-
// streaming credentials provider that emits redis credentials which will trigger connection re-authentication
169-
// token manager is used to emit updated credentials
170-
TokenBasedRedisCredentialsProvider credentialsProvider = new TokenBasedRedisCredentialsProvider(tokenManager);
171-
172-
RedisURI uri = RedisURI.builder().withTimeout(Duration.ofSeconds(1)).withClientName("streaming_cred_test")
173-
.withHost(TestSettings.host()).withPort(TestSettings.port()).withAuthentication(credentialsProvider).build();
174-
175-
// create test user with initial credentials set to 'testPassword1'
176-
createTestUser(defaultConnection, testUser, testPassword1);
177-
tokenManager.emitToken(testToken(testUser, testPassword1));
178-
179-
StatefulRedisConnection<String, String> connection = client.connect(StringCodec.UTF8, uri);
180-
assertThat(connection.sync().aclWhoami()).isEqualTo(testUser);
181-
182-
// update test user credentials in Redis server (password changed to testPassword2)
183-
// then emit updated credentials trough streaming credentials provider
184-
// and trigger re-connect to force re-authentication
185-
// updated credentials should be used for re-authentication
186-
updateTestUser(defaultConnection, testUser, testPassword2);
187-
tokenManager.emitToken(testToken(testUser, testPassword2));
188-
connection.sync().quit();
189-
190-
Delay.delay(Duration.ofMillis(100));
191-
assertThat(connection.sync().ping()).isEqualTo("PONG");
192-
193-
String res = connection.sync().aclWhoami();
194-
assertThat(res).isEqualTo(testUser);
186+
private SimpleToken testToken(String username, char[] password) {
187+
return new SimpleToken(String.valueOf(password), Instant.now().plusMillis(500).toEpochMilli(),
188+
Instant.now().toEpochMilli(), Collections.singletonMap("oid", username));
189+
}
195190

196-
defaultConnection.close();
197-
connection.close();
198191
}

src/test/java/io/lettuce/core/cluster/RedisClusterStreamingCredentialsProviderlIntegrationTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ void nodeSelectionApiShouldWork() {
140140
@Test
141141
void shouldPerformNodeConnectionReauth() {
142142
ClusterClientOptions origClientOptions = redisClient.getClusterClientOptions();
143-
origClientOptions.mutate().reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build();
144143
redisClient.setOptions(origClientOptions.mutate()
145144
.reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build());
146145

src/test/java/io/lettuce/examples/TokenBasedAuthExample.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
package io.lettuce.examples;
22

3+
import com.microsoft.aad.msal4j.ClientCredentialFactory;
4+
import com.microsoft.aad.msal4j.ClientCredentialParameters;
5+
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
6+
import com.microsoft.aad.msal4j.IAuthenticationResult;
7+
import com.microsoft.aad.msal4j.IClientSecret;
38
import io.lettuce.authx.TokenBasedRedisCredentialsProvider;
49
import io.lettuce.core.ClientOptions;
510
import io.lettuce.core.RedisClient;
611
import io.lettuce.core.RedisURI;
712
import io.lettuce.core.SocketOptions;
8-
import io.lettuce.core.TimeoutOptions;
913
import io.lettuce.core.api.StatefulRedisConnection;
1014
import io.lettuce.core.codec.StringCodec;
1115
import redis.clients.authentication.core.IdentityProviderConfig;
1216
import redis.clients.authentication.core.TokenAuthConfig;
1317
import redis.clients.authentication.entraid.EntraIDTokenAuthConfigBuilder;
1418

19+
import java.net.MalformedURLException;
1520
import java.time.Duration;
1621
import java.util.Collections;
1722
import java.util.Set;
23+
import java.util.concurrent.ExecutionException;
24+
import java.util.concurrent.Future;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
1827

1928
public class TokenBasedAuthExample {
2029

@@ -24,11 +33,34 @@ public static void main(String[] args) {
2433
Set<String> scopes = Collections.singleton("https://redis.azure.com/.default");
2534

2635
String User1_clientId = System.getenv("USER1_CLIENT_ID");
36+
String User1_objectid = System.getenv("USER1_OBJECT_ID");
2737
String User1_secret = System.getenv("USER1_SECRET");
2838

2939
String User2_clientId = System.getenv("USER2_CLIENT_ID");
3040
String User2_secret = System.getenv("USER2_SECRET");
3141

42+
try {
43+
IClientSecret cred = ClientCredentialFactory.createFromSecret(User1_secret);
44+
ConfidentialClientApplication app = ConfidentialClientApplication.builder(User1_clientId, cred).authority(authority)
45+
.build();
46+
ClientCredentialParameters params = ClientCredentialParameters.builder(scopes).skipCache(true).build();
47+
Future<IAuthenticationResult> tokenRequest1 = app.acquireToken(params);
48+
IAuthenticationResult t1 = tokenRequest1.get();
49+
Future<IAuthenticationResult> tokenRequest2 = app.acquireToken(params);
50+
IAuthenticationResult t2 = tokenRequest2.get();
51+
System.out.println(t1.accessToken());
52+
System.out.println(t2.accessToken());
53+
assertThat(t1.accessToken()).isNotEqualTo(t2.accessToken());
54+
} catch (InterruptedException e) {
55+
throw new RuntimeException(e);
56+
} catch (ExecutionException e) {
57+
throw new RuntimeException(e);
58+
} catch (MalformedURLException e) {
59+
throw new RuntimeException(e);
60+
}
61+
62+
ClientCredentialParameters.builder(scopes).skipCache(true).build();
63+
3264
// User 1
3365
// from redis-authx-entraind
3466
IdentityProviderConfig config1 = EntraIDTokenAuthConfigBuilder.builder().authority(authority).clientId(User1_clientId)
@@ -62,7 +94,7 @@ public static void main(String[] args) {
6294
ClientOptions clientOptions = ClientOptions.builder()
6395
.socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(5)).build())
6496
.disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
65-
.timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(1))).build();
97+
.reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build();
6698

6799
// RedisClient using user1 credentials by default
68100
RedisClient redisClient = RedisClient.create(redisURI1);

0 commit comments

Comments
 (0)