diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java index ded57921603c2..4dad6a703eb00 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AuthorizationCodeCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.IdentityClient; @@ -27,6 +28,7 @@ public class AuthorizationCodeCredential implements TokenCredential { private final String authCode; private final URI redirectUri; private final IdentityClient identityClient; + private final IdentityClientOptions identityClientOptions; private final AtomicReference cachedToken; private final ClientLogger logger = new ClientLogger(AuthorizationCodeCredential.class); @@ -46,6 +48,7 @@ public class AuthorizationCodeCredential implements TokenCredential { .clientId(clientId) .identityClientOptions(identityClientOptions) .build(); + this.identityClientOptions = identityClientOptions; this.cachedToken = new AtomicReference<>(); this.authCode = authCode; this.redirectUri = redirectUri; @@ -71,4 +74,9 @@ public Mono getToken(TokenRequestContext request) { .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureCliCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureCliCredential.java index 7f62a1eb8cd98..9f4f685540ccc 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureCliCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AzureCliCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.IdentityClient; @@ -21,6 +22,7 @@ @Immutable public class AzureCliCredential implements TokenCredential { private final IdentityClient identityClient; + private final IdentityClientOptions identityClientOptions; private final ClientLogger logger = new ClientLogger(AzureCliCredential.class); /** @@ -29,6 +31,7 @@ public class AzureCliCredential implements TokenCredential { */ AzureCliCredential(IdentityClientOptions identityClientOptions) { identityClient = new IdentityClientBuilder().identityClientOptions(identityClientOptions).build(); + this.identityClientOptions = identityClientOptions; } @Override @@ -37,4 +40,9 @@ public Mono getToken(TokenRequestContext request) { .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java index ac36c7c6ac1a7..1dd11419f4a27 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ChainedTokenCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.exception.ClientAuthenticationException; import com.azure.core.util.logging.ClientLogger; @@ -25,6 +26,7 @@ */ @Immutable public class ChainedTokenCredential implements TokenCredential { + private volatile TokenRefreshOptions tokenRefreshOptions; private final ClientLogger logger = new ClientLogger(getClass()); private final List credentials; private final String unavailableError = this.getClass().getSimpleName() + " authentication failed. ---> "; @@ -55,7 +57,7 @@ public Mono getToken(TokenRequestContext request) { logger.info("Azure Identity => Attempted credential {} is unavailable.", p.getClass().getSimpleName()); return Mono.empty(); - }), 1) + }).doOnNext(t -> tokenRefreshOptions = p.getTokenRefreshOptions()), 1) .next() .switchIfEmpty(Mono.defer(() -> { // Chain Exceptions. @@ -69,6 +71,11 @@ public Mono getToken(TokenRequestContext request) { })); } + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return tokenRefreshOptions; + } + /** * Get the read-only list of credentials sequentially used to attempt authentication. diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java index 1465c1a67d8b6..cb4c46a1b995a 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientCertificateCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.IdentityClient; @@ -28,6 +29,7 @@ @Immutable public class ClientCertificateCredential implements TokenCredential { private final IdentityClient identityClient; + private final IdentityClientOptions identityClientOptions; private final ClientLogger logger = new ClientLogger(ClientCertificateCredential.class); /** @@ -48,6 +50,7 @@ public class ClientCertificateCredential implements TokenCredential { .certificatePassword(certificatePassword) .identityClientOptions(identityClientOptions) .build(); + this.identityClientOptions = identityClientOptions; } @Override @@ -58,4 +61,9 @@ public Mono getToken(TokenRequestContext request) { .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java index ba48a5cb34a0f..7b8e438f942a0 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ClientSecretCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.IdentityClient; @@ -28,6 +29,7 @@ @Immutable public class ClientSecretCredential implements TokenCredential { private final IdentityClient identityClient; + private final IdentityClientOptions identityClientOptions; private final ClientLogger logger = new ClientLogger(ClientSecretCredential.class); /** @@ -48,6 +50,7 @@ public class ClientSecretCredential implements TokenCredential { .clientSecret(clientSecret) .identityClientOptions(identityClientOptions) .build(); + this.identityClientOptions = identityClientOptions; } @Override @@ -58,4 +61,9 @@ public Mono getToken(TokenRequestContext request) { .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/CredentialBuilderBase.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/CredentialBuilderBase.java index 13c76e29c1bed..5c2c73d9b019e 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/CredentialBuilderBase.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/CredentialBuilderBase.java @@ -3,6 +3,7 @@ package com.azure.identity; +import com.azure.core.credential.SimpleTokenCache; import com.azure.core.http.HttpClient; import com.azure.core.http.HttpPipeline; import com.azure.core.http.ProxyOptions; @@ -95,8 +96,16 @@ public T httpClient(HttpClient client) { * token will be considered expired at and after the time of (actual * expiry - token refresh offset). The default offset is 2 minutes. * - * This is useful when network is congested and a request containing the - * token takes longer than normal to get to the server. + * This is used in {@link SimpleTokenCache} and {@link com.azure.core.http.policy.BearerTokenAuthenticationPolicy} + * to proactively retrieve a more up-to-date token before the cached token gets too close to its expiry. + * + * Extending this offset is recommended if it takes > 2 minutes to reach the service (application is running on + * high load), or you would like to simply keep a more up-to-date token in the cache (more robust against token + * refresh API down times). The user is responsible for specifying a valid offset. + * + * When a proactive token refresh fails but the previously cached token is still valid, + * {@link com.azure.core.http.policy.BearerTokenAuthenticationPolicy} will NOT fail but return the previous valid + * token. Another proactive refresh will be attempted in 30 seconds. * * @param tokenRefreshOffset the duration before the actual expiry of a token to refresh it * @return An updated instance of this builder with the token refresh offset set as specified. diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java index 54ede1c8522da..d977074ca8281 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredential.java @@ -5,6 +5,8 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; +import com.azure.identity.implementation.IdentityClientOptions; import java.util.List; @@ -22,6 +24,7 @@ */ @Immutable public final class DefaultAzureCredential extends ChainedTokenCredential { + private final IdentityClientOptions identityClientOptions; /** * Creates default DefaultAzureCredential instance to use. This will use AZURE_CLIENT_ID, @@ -32,9 +35,16 @@ public final class DefaultAzureCredential extends ChainedTokenCredential { * token cache. * * @param tokenCredentials the list of credentials to execute for authentication. + * @param identityClientOptions the options for configuring the identity client. */ - DefaultAzureCredential(List tokenCredentials) { + DefaultAzureCredential(List tokenCredentials, IdentityClientOptions identityClientOptions) { super(tokenCredentials); + this.identityClientOptions = identityClientOptions; + } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredentialBuilder.java index fbe9992ced9bd..a36f7810b63a8 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DefaultAzureCredentialBuilder.java @@ -95,7 +95,7 @@ public DefaultAzureCredentialBuilder executorService(ExecutorService executorSer * @return a {@link DefaultAzureCredential} with the current configurations. */ public DefaultAzureCredential build() { - return new DefaultAzureCredential(getCredentialsChain()); + return new DefaultAzureCredential(getCredentialsChain(), identityClientOptions); } private ArrayList getCredentialsChain() { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java index 28935a6962318..561e23661cac7 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.IdentityClient; @@ -26,6 +27,7 @@ public class DeviceCodeCredential implements TokenCredential { private final Consumer challengeConsumer; private final IdentityClient identityClient; + private final IdentityClientOptions identityClientOptions; private final AtomicReference cachedToken; private final String authorityHost; private final boolean automaticAuthentication; @@ -50,6 +52,7 @@ public class DeviceCodeCredential implements TokenCredential { .identityClientOptions(identityClientOptions) .build(); this.cachedToken = new AtomicReference<>(); + this.identityClientOptions = identityClientOptions; this.authorityHost = identityClientOptions.getAuthorityHost(); this.automaticAuthentication = automaticAuthentication; if (identityClientOptions.getAuthenticationRecord() != null) { @@ -126,4 +129,9 @@ private AccessToken updateCache(MsalToken msalToken) { identityClient.getTenantId()))); return msalToken; } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredential.java index 6b7f3f6d93792..e4fd809ace524 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/EnvironmentCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.Configuration; import com.azure.core.util.logging.ClientLogger; @@ -128,6 +129,11 @@ public Mono getToken(TokenRequestContext request) { } } + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } + private boolean verifyNotNull(String... configs) { for (String config: configs) { if (config == null) { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/IntelliJCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/IntelliJCredential.java index 0ca5a263fdd7a..e2bd9f305e472 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/IntelliJCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/IntelliJCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; @@ -30,6 +31,7 @@ public class IntelliJCredential implements TokenCredential { private static final String AZURE_TOOLS_FOR_INTELLIJ_CLIENT_ID = "61d65f5a-6e3b-468b-af73-a033f5098c5c"; private final IdentityClient identityClient; + private final IdentityClientOptions identityClientOptions; private final AtomicReference cachedToken; private final ClientLogger logger = new ClientLogger(IntelliJCredential.class); @@ -72,6 +74,7 @@ public class IntelliJCredential implements TokenCredential { .build(); this.cachedToken = new AtomicReference<>(); + this.identityClientOptions = identityClientOptions; } @Override @@ -92,4 +95,9 @@ public Mono getToken(TokenRequestContext request) { .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java index 49b68c4bf1b2e..f02fa9e1f6852 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/InteractiveBrowserCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.IdentityClient; @@ -31,6 +32,7 @@ public class InteractiveBrowserCredential implements TokenCredential { private final int port; private final IdentityClient identityClient; + private final IdentityClientOptions identityClientOptions; private final AtomicReference cachedToken; private final boolean automaticAuthentication; private final String authorityHost; @@ -55,6 +57,7 @@ public class InteractiveBrowserCredential implements TokenCredential { .clientId(clientId) .identityClientOptions(identityClientOptions) .build(); + this.identityClientOptions = identityClientOptions; cachedToken = new AtomicReference<>(); this.authorityHost = identityClientOptions.getAuthorityHost(); this.automaticAuthentication = automaticAuthentication; @@ -115,6 +118,11 @@ public Mono authenticate() { return authenticate(new TokenRequestContext().addScopes(defaultScope)); } + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } + private AccessToken updateCache(MsalToken msalToken) { cachedToken.set( new MsalAuthenticationAccount( @@ -122,5 +130,4 @@ private AccessToken updateCache(MsalToken msalToken) { identityClient.getTenantId()))); return msalToken; } - } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java index 69cf15ee04255..eb78d31d39b18 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.Configuration; import com.azure.core.util.logging.ClientLogger; @@ -22,6 +23,7 @@ public final class ManagedIdentityCredential implements TokenCredential { private final AppServiceMsiCredential appServiceMSICredential; private final VirtualMachineMsiCredential virtualMachineMSICredential; + private final IdentityClientOptions identityClientOptions; private final ClientLogger logger = new ClientLogger(ManagedIdentityCredential.class); /** @@ -42,6 +44,7 @@ public final class ManagedIdentityCredential implements TokenCredential { virtualMachineMSICredential = new VirtualMachineMsiCredential(clientId, identityClient); appServiceMSICredential = null; } + this.identityClientOptions = identityClientOptions; LoggingUtil.logAvailableEnvironmentVariables(logger, configuration); } @@ -69,4 +72,9 @@ public Mono getToken(TokenRequestContext request) { .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java index 41bc18e143601..f7fab9afcfaf0 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/SharedTokenCacheCredential.java @@ -5,6 +5,7 @@ import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.Configuration; import com.azure.core.util.logging.ClientLogger; @@ -29,6 +30,7 @@ public class SharedTokenCacheCredential implements TokenCredential { private final AtomicReference cachedToken; private final IdentityClient identityClient; + private final IdentityClientOptions identityClientOptions; private final ClientLogger logger = new ClientLogger(SharedTokenCacheCredential.class); /** @@ -65,6 +67,7 @@ public class SharedTokenCacheCredential implements TokenCredential { .identityClientOptions(identityClientOptions) .build(); this.cachedToken = new AtomicReference<>(); + this.identityClientOptions = identityClientOptions; LoggingUtil.logAvailableEnvironmentVariables(logger, configuration); } @@ -89,4 +92,9 @@ public Mono getToken(TokenRequestContext request) { .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java index 135cbef914f16..4186aa8a0c112 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/UsernamePasswordCredential.java @@ -6,6 +6,7 @@ import com.azure.core.annotation.Immutable; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.IdentityClient; @@ -29,6 +30,7 @@ public class UsernamePasswordCredential implements TokenCredential { private final String username; private final String password; private final IdentityClient identityClient; + private final IdentityClientOptions identityClientOptions; private final String authorityHost; private final AtomicReference cachedToken; private final ClientLogger logger = new ClientLogger(UsernamePasswordCredential.class); @@ -55,6 +57,7 @@ public class UsernamePasswordCredential implements TokenCredential { .identityClientOptions(identityClientOptions) .build(); cachedToken = new AtomicReference<>(); + this.identityClientOptions = identityClientOptions; this.authorityHost = identityClientOptions.getAuthorityHost(); } @@ -107,4 +110,9 @@ private AccessToken updateCache(MsalToken msalToken) { identityClient.getTenantId()))); return msalToken; } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/VisualStudioCodeCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/VisualStudioCodeCredential.java index 9e62610a6b870..3c5799f7abe7f 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/VisualStudioCodeCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/VisualStudioCodeCredential.java @@ -5,6 +5,7 @@ import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.credential.TokenRequestContext; import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; @@ -24,6 +25,7 @@ */ public class VisualStudioCodeCredential implements TokenCredential { private final IdentityClient identityClient; + private final IdentityClientOptions identityClientOptions; private final AtomicReference cachedToken; private final String cloudInstance; private final ClientLogger logger = new ClientLogger(VisualStudioCodeCredential.class); @@ -64,6 +66,7 @@ public class VisualStudioCodeCredential implements TokenCredential { .build(); this.cachedToken = new AtomicReference<>(); + this.identityClientOptions = identityClientOptions; } @Override @@ -84,4 +87,9 @@ public Mono getToken(TokenRequestContext request) { .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request)) .doOnError(error -> LoggingUtil.logTokenError(logger, request, error)); } + + @Override + public TokenRefreshOptions getTokenRefreshOptions() { + return identityClientOptions.getTokenRefreshOptions(); + } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index deda07e206bdd..78d1656f740d8 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -265,8 +265,7 @@ public Mono authenticateWithIntelliJ(TokenRequestContext request) { ConfidentialClientApplication application = applicationBuilder.build(); return Mono.fromFuture(application.acquireToken( ClientCredentialParameters.builder(new HashSet<>(request.getScopes())) - .build())) - .map(ar -> new MsalToken(ar, options)); + .build())).map(MsalToken::new); } catch (MalformedURLException e) { return Mono.error(e); } @@ -280,7 +279,7 @@ public Mono authenticateWithIntelliJ(TokenRequestContext request) { .build(); return publicClientApplicationAccessor.getValue() - .flatMap(pc -> Mono.fromFuture(pc.acquireToken(parameters)).map(ar -> new MsalToken(ar, options))); + .flatMap(pc -> Mono.fromFuture(pc.acquireToken(parameters)).map(MsalToken::new)); } else { throw logger.logExceptionAsError(new CredentialUnavailableException( @@ -408,7 +407,7 @@ public Mono authenticateWithConfidentialClient(TokenRequestContext return confidentialClientApplicationAccessor.getValue() .flatMap(confidentialClient -> Mono.fromFuture(() -> confidentialClient.acquireToken( ClientCredentialParameters.builder(new HashSet<>(request.getScopes())).build())) - .map(ar -> new MsalToken(ar, options))); + .map(MsalToken::new)); } private HttpPipeline setupPipeline(HttpClient httpClient) { @@ -437,7 +436,7 @@ public Mono authenticateWithUsernamePassword(TokenRequestContext requ new HashSet<>(request.getScopes()), username, password.toCharArray()).build())) .onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with username and " + "password", null, t)) - .map(ar -> new MsalToken(ar, options))); + .map(MsalToken::new)); } /** @@ -449,31 +448,32 @@ public Mono authenticateWithUsernamePassword(TokenRequestContext requ */ public Mono authenticateWithPublicClientCache(TokenRequestContext request, IAccount account) { return publicClientApplicationAccessor.getValue() - .flatMap(pc -> Mono.fromFuture(() -> { - SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder( - new HashSet<>(request.getScopes())); + .flatMap(pc -> Mono.fromFuture(() -> { + SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder( + new HashSet<>(request.getScopes())); + if (account != null) { + parametersBuilder = parametersBuilder.account(account); + } + try { + return pc.acquireTokenSilently(parametersBuilder.build()); + } catch (MalformedURLException e) { + return getFailedCompletableFuture(logger.logExceptionAsError(new RuntimeException(e))); + } + }).map(MsalToken::new) + .filter(t -> OffsetDateTime.now().isBefore(t.getExpiresAt().minus( + options.getTokenRefreshOptions().getOffset()))) + .switchIfEmpty(Mono.fromFuture(() -> { + SilentParameters.SilentParametersBuilder forceParametersBuilder = SilentParameters.builder( + new HashSet<>(request.getScopes())).forceRefresh(true); if (account != null) { - parametersBuilder = parametersBuilder.account(account); + forceParametersBuilder = forceParametersBuilder.account(account); } try { - return pc.acquireTokenSilently(parametersBuilder.build()); + return pc.acquireTokenSilently(forceParametersBuilder.build()); } catch (MalformedURLException e) { return getFailedCompletableFuture(logger.logExceptionAsError(new RuntimeException(e))); } - }).map(ar -> new MsalToken(ar, options)) - .filter(t -> !t.isExpired()) - .switchIfEmpty(Mono.fromFuture(() -> { - SilentParameters.SilentParametersBuilder forceParametersBuilder = SilentParameters.builder( - new HashSet<>(request.getScopes())).forceRefresh(true); - if (account != null) { - forceParametersBuilder = forceParametersBuilder.account(account); - } - try { - return pc.acquireTokenSilently(forceParametersBuilder.build()); - } catch (MalformedURLException e) { - return getFailedCompletableFuture(logger.logExceptionAsError(new RuntimeException(e))); - } - }).map(result -> new MsalToken(result, options)))); + }).map(MsalToken::new))); } /** @@ -484,16 +484,17 @@ public Mono authenticateWithPublicClientCache(TokenRequestContext req */ public Mono authenticateWithConfidentialClientCache(TokenRequestContext request) { return confidentialClientApplicationAccessor.getValue() - .flatMap(confidentialClient -> Mono.fromFuture(() -> { - SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder( - new HashSet<>(request.getScopes())); - try { - return confidentialClient.acquireTokenSilently(parametersBuilder.build()); - } catch (MalformedURLException e) { - return getFailedCompletableFuture(logger.logExceptionAsError(new RuntimeException(e))); - } - }).map(ar -> (AccessToken) new MsalToken(ar, options)) - .filter(t -> !t.isExpired())); + .flatMap(confidentialClient -> Mono.fromFuture(() -> { + SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters.builder( + new HashSet<>(request.getScopes())); + try { + return confidentialClient.acquireTokenSilently(parametersBuilder.build()); + } catch (MalformedURLException e) { + return getFailedCompletableFuture(logger.logExceptionAsError(new RuntimeException(e))); + } + }).map(ar -> (AccessToken) new MsalToken(ar)) + .filter(t -> OffsetDateTime.now().isBefore(t.getExpiresAt().minus( + options.getTokenRefreshOptions().getOffset())))); } /** @@ -516,7 +517,7 @@ public Mono authenticateWithDeviceCode(TokenRequestContext request, OffsetDateTime.now().plusSeconds(dc.expiresIn()), dc.message()))).build(); return pc.acquireToken(parameters); }).onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with device code", null, t)) - .map(ar -> new MsalToken(ar, options))); + .map(MsalToken::new)); } /** @@ -536,8 +537,7 @@ public Mono authenticateWithVsCodeCredential(TokenRequestContext requ .build(); return publicClientApplicationAccessor.getValue() - .flatMap(pc -> Mono.fromFuture(pc.acquireToken(parameters)) - .map(ar -> new MsalToken(ar, options))); + .flatMap(pc -> Mono.fromFuture(pc.acquireToken(parameters)).map(MsalToken::new)); } /** @@ -556,7 +556,7 @@ public Mono authenticateWithAuthorizationCode(TokenRequestContext req .scopes(new HashSet<>(request.getScopes())) .build())) .onErrorMap(t -> new ClientAuthenticationException("Failed to acquire token with authorization code", - null, t)).map(ar -> new MsalToken(ar, options))); + null, t)).map(MsalToken::new)); } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index e32789a742f12..885ba681372d8 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -3,6 +3,7 @@ package com.azure.identity.implementation; +import com.azure.core.credential.TokenRefreshOptions; import com.azure.core.http.HttpClient; import com.azure.core.http.HttpPipeline; import com.azure.core.http.ProxyOptions; @@ -46,7 +47,7 @@ public final class IdentityClientOptions { private ProxyOptions proxyOptions; private HttpPipeline httpPipeline; private ExecutorService executorService; - private Duration tokenRefreshOffset = Duration.ofMinutes(2); + private IdentityTokenRefreshOptions tokenRefreshOptions = new IdentityTokenRefreshOptions(); private HttpClient httpClient; private boolean allowUnencryptedCache; private boolean sharedTokenCacheEnabled; @@ -186,10 +187,10 @@ public ExecutorService getExecutorService() { } /** - * @return how long before the actual token expiry to refresh the token. + * @return the options to configure the token refresh behavior. */ - public Duration getTokenRefreshOffset() { - return tokenRefreshOffset; + public TokenRefreshOptions getTokenRefreshOptions() { + return tokenRefreshOptions; } /** @@ -206,7 +207,7 @@ public Duration getTokenRefreshOffset() { */ public IdentityClientOptions setTokenRefreshOffset(Duration tokenRefreshOffset) { Objects.requireNonNull(tokenRefreshOffset, "The token refresh offset cannot be null."); - this.tokenRefreshOffset = tokenRefreshOffset; + this.tokenRefreshOptions.setOffset(tokenRefreshOffset); return this; } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityTokenRefreshOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityTokenRefreshOptions.java new file mode 100644 index 0000000000000..50ca53818fc7b --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityTokenRefreshOptions.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.implementation; + +import com.azure.core.credential.TokenRefreshOptions; + +import java.time.Duration; + +/** + * The options to configure the token refresh behavior for azure-identity credentials. + */ +public class IdentityTokenRefreshOptions extends TokenRefreshOptions { + private Duration offset = Duration.ofMinutes(2); + + @Override + public Duration getOffset() { + return offset; + } + + /** + * Sets the duration value representing the amount of time to subtract from the token expiry time. + * @param offset the duration value representing the amount of time to subtract from the token expiry time + */ + public void setOffset(Duration offset) { + this.offset = offset; + } +} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/MsalToken.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/MsalToken.java index a8b2d3754812b..8299c42422d21 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/MsalToken.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/MsalToken.java @@ -22,7 +22,7 @@ public final class MsalToken extends AccessToken { * * @param msalResult the raw authentication result returned by MSAL */ - public MsalToken(IAuthenticationResult msalResult, IdentityClientOptions options) { + public MsalToken(IAuthenticationResult msalResult) { super(msalResult.accessToken(), OffsetDateTime.ofInstant(msalResult.expiresOnDate().toInstant(), ZoneOffset.UTC)); authenticationResult = msalResult; diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java index 229e127344043..e26574ebb32f2 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java @@ -4,7 +4,6 @@ package com.azure.identity.util; import com.azure.core.credential.AccessToken; -import com.azure.identity.implementation.IdentityClientOptions; import com.azure.identity.implementation.MsalToken; import com.microsoft.aad.msal4j.IAccount; import com.microsoft.aad.msal4j.IAuthenticationResult; @@ -84,7 +83,7 @@ public Date expiresOnDate() { */ public static Mono getMockMsalToken(String accessToken, OffsetDateTime expiresOn) { return Mono.fromFuture(getMockAuthenticationResult(accessToken, expiresOn)) - .map(ar -> new MsalToken(ar, new IdentityClientOptions())); + .map(MsalToken::new); } /**