-
Notifications
You must be signed in to change notification settings - Fork 985
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Token based authentication integration with core extension (#3063)
* Support for StreamingCredentials This enables use cases like credential rotation and token based auth without client disconnect. Especially with Pub/Sub clients will reduce the chnance of missing events. * Tests & publish ReauthEvent * Clean up & Format & Add ReauthenticateEvent test * Conditionally enable connection reauthentication based on client setting DEFAULT_REAUTHENTICATE_BEHAVIOUR * Client setting for enabling reauthentication - Moved Authentication handler to DefaultEndpoint - updated since 6.6.0 * formating * resolve conflict with main * format * dispath using connection handler * Support multi with re-auth Defer the re-auth operation in case there is on-going multi Tx in lettuce need to be externally synchronised when used in multithreaded env. Since re-auth happens from different thread we need to make sure it does not happen while there is ongoing transaction. * Fix EndpointId missing in events * format * Add unit tests for setCredenatials * Skip preProcessing of auth command to avoid replacing the credential provider with static one provider Add unit tests for setCredentials * clean up - remove dead code * Moved almost all code inside the new handler * fix inTransaction lock with dispatch command batch * Remove StreamingCredentialsProvider interface. move credentials() method to RedisCredentialsProvider. Resolve issue with unsafe cast after extending RedisCredentialsProvider with supportsStreaming() method * Add authentication handler to ClusterPubSub connections * Token based auth integration with core extension Provide a way for lettuce clients to use token-based authentication. TOKENs come with a TTL. After a Redis client authenticates with a TOKEN, if they didn't renew their authentication we need to evict (close) them. The suggested approach is to leverage the existing CredentialsProvider and add support for streaming credentials to handle token refresh scenarios. Each time a new token is received connection is reauthenticated. * rebase to address "oid" core-autx lib change formating * Add EntraId integration tests Verify authentication using Azure AD with service principals * StreamingCredentialsProvider replaced with RedisCredentialsProvider.supportsStreaming() * pub/sub test basic functionality with entraid auth * Update src/main/java/io/lettuce/authx/TokenBasedRedisCredentialsProvider.java Co-authored-by: Tihomir Krasimirov Mateev <tihomir.mateev@redis.com> * Addressing review comments from @tishun * Bump redis-authx-core & redis-authx-entraid from 0.1.0-SNAPSHOT to 0.1.1-beta1 * add java doc for TokenBasedRedisCredentialsProvider --------- Co-authored-by: Tihomir Mateev <tihomir.mateev@gmail.com> Co-authored-by: Tihomir Krasimirov Mateev <tihomir.mateev@redis.com>
- Loading branch information
1 parent
14de5b8
commit eff243d
Showing
27 changed files
with
2,245 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
src/main/java/io/lettuce/authx/TokenBasedRedisCredentialsProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* Copyright 2024, Redis Ltd. and Contributors | ||
* All rights reserved. | ||
* | ||
* Licensed under the MIT License. | ||
*/ | ||
package io.lettuce.authx; | ||
|
||
import io.lettuce.core.RedisCredentials; | ||
import io.lettuce.core.RedisCredentialsProvider; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import reactor.core.publisher.Flux; | ||
import reactor.core.publisher.Mono; | ||
import reactor.core.publisher.Sinks; | ||
import redis.clients.authentication.core.Token; | ||
import redis.clients.authentication.core.TokenAuthConfig; | ||
import redis.clients.authentication.core.TokenListener; | ||
import redis.clients.authentication.core.TokenManager; | ||
|
||
/** | ||
* A {@link RedisCredentialsProvider} implementation that supports token-based authentication for Redis. | ||
* <p> | ||
* This provider uses a {@link TokenManager} to manage and renew tokens, ensuring that the Redis client can authenticate with | ||
* Redis using a dynamically updated token. This is particularly useful in scenarios where Redis access is controlled via | ||
* token-based authentication, such as when Redis is integrated with an identity provider like EntraID. | ||
* </p> | ||
* <p> | ||
* The provider supports streaming of credentials and automatically emits new credentials whenever a token is renewed. It must | ||
* be used with {@link io.lettuce.core.ClientOptions.ReauthenticateBehavior#ON_NEW_CREDENTIALS} to automatically re-authenticate | ||
* connections whenever new tokens are emitted by the provider. | ||
* </p> | ||
* <p> | ||
* The lifecycle of this provider is externally managed. It should be closed when there are no longer any connections using it, | ||
* to stop the token management process and release resources. | ||
* </p> | ||
* | ||
* @since 6.6 | ||
*/ | ||
public class TokenBasedRedisCredentialsProvider implements RedisCredentialsProvider, AutoCloseable { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(TokenBasedRedisCredentialsProvider.class); | ||
|
||
private final TokenManager tokenManager; | ||
|
||
private final Sinks.Many<RedisCredentials> credentialsSink = Sinks.many().replay().latest(); | ||
|
||
private TokenBasedRedisCredentialsProvider(TokenManager tokenManager) { | ||
this.tokenManager = tokenManager; | ||
} | ||
|
||
private void init() { | ||
|
||
TokenListener listener = new TokenListener() { | ||
|
||
@Override | ||
public void onTokenRenewed(Token token) { | ||
String username = token.getUser(); | ||
char[] pass = token.getValue().toCharArray(); | ||
RedisCredentials credentials = RedisCredentials.just(username, pass); | ||
credentialsSink.tryEmitNext(credentials); | ||
} | ||
|
||
@Override | ||
public void onError(Exception exception) { | ||
log.error("Token renew failed!", exception); | ||
} | ||
|
||
}; | ||
|
||
try { | ||
tokenManager.start(listener, false); | ||
} catch (Exception e) { | ||
credentialsSink.tryEmitError(e); | ||
tokenManager.stop(); | ||
throw new RuntimeException("Failed to start TokenManager", e); | ||
} | ||
} | ||
|
||
/** | ||
* Resolve the latest available credentials as a Mono. | ||
* <p> | ||
* This method returns a Mono that emits the most recent set of Redis credentials. The Mono will complete once the | ||
* credentials are emitted. If no credentials are available at the time of subscription, the Mono will wait until | ||
* credentials are available. | ||
* | ||
* @return a Mono that emits the latest Redis credentials | ||
*/ | ||
@Override | ||
public Mono<RedisCredentials> resolveCredentials() { | ||
|
||
return credentialsSink.asFlux().next(); | ||
} | ||
|
||
/** | ||
* Expose the Flux for all credential updates. | ||
* <p> | ||
* This method returns a Flux that emits all updates to the Redis credentials. Subscribers will receive the latest | ||
* credentials whenever they are updated. The Flux will continue to emit updates until the provider is shut down. | ||
* | ||
* @return a Flux that emits all updates to the Redis credentials | ||
*/ | ||
@Override | ||
public Flux<RedisCredentials> credentials() { | ||
|
||
return credentialsSink.asFlux().onBackpressureLatest(); // Provide a continuous stream of credentials | ||
} | ||
|
||
@Override | ||
public boolean supportsStreaming() { | ||
return true; | ||
} | ||
|
||
/** | ||
* Stop the credentials provider and clean up resources. | ||
* <p> | ||
* This method stops the TokenManager and completes the credentials sink, ensuring that all resources are properly released. | ||
* It should be called when the credentials provider is no longer needed. | ||
*/ | ||
@Override | ||
public void close() { | ||
credentialsSink.tryEmitComplete(); | ||
tokenManager.stop(); | ||
} | ||
|
||
public static TokenBasedRedisCredentialsProvider create(TokenAuthConfig tokenAuthConfig) { | ||
return create(new TokenManager(tokenAuthConfig.getIdentityProviderConfig().getProvider(), | ||
tokenAuthConfig.getTokenManagerConfig())); | ||
} | ||
|
||
public static TokenBasedRedisCredentialsProvider create(TokenManager tokenManager) { | ||
TokenBasedRedisCredentialsProvider credentialManager = new TokenBasedRedisCredentialsProvider(tokenManager); | ||
credentialManager.init(); | ||
return credentialManager; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.