-
Notifications
You must be signed in to change notification settings - Fork 427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Upgrading to new Key Vault (and MSAL) libraries #1375
Changes from all commits
f4555d6
892eb2b
088d2f7
30e7fdf
2d62262
a6b75cd
5e37322
1df8828
8c9a944
f7f12d9
cc0ac7f
86fc00c
8fec202
9f44f0f
dab428f
398c2d8
1b363f7
948939f
6d014b5
dcf2f79
62a83e4
73d160f
fba2a23
f006a63
b2db031
47a7b94
9b1cac0
fcf327e
23b7f6a
4121bc5
37c676d
8e8517b
ee3b5c8
48c6dac
6baad02
a053426
0a3cb0f
684ecce
e4206e1
d7c2305
be1d739
1423d93
e077ffe
f749361
b08233c
49b802e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,86 +5,117 @@ | |
|
||
package com.microsoft.sqlserver.jdbc; | ||
|
||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.Future; | ||
|
||
import com.microsoft.aad.adal4j.AuthenticationContext; | ||
import com.microsoft.aad.adal4j.AuthenticationResult; | ||
import com.microsoft.aad.adal4j.ClientCredential; | ||
import com.microsoft.azure.keyvault.authentication.KeyVaultCredentials; | ||
|
||
import com.azure.core.annotation.Immutable; | ||
import com.azure.core.credential.AccessToken; | ||
import com.azure.core.credential.TokenCredential; | ||
import com.azure.core.credential.TokenRequestContext; | ||
import com.azure.core.util.logging.ClientLogger; | ||
import com.microsoft.aad.msal4j.ClientCredentialFactory; | ||
import com.microsoft.aad.msal4j.ClientCredentialParameters; | ||
import com.microsoft.aad.msal4j.ConfidentialClientApplication; | ||
import com.microsoft.aad.msal4j.IAuthenticationResult; | ||
import com.microsoft.aad.msal4j.IClientCredential; | ||
import com.microsoft.aad.msal4j.SilentParameters; | ||
import java.net.MalformedURLException; | ||
import java.time.OffsetDateTime; | ||
import java.time.ZoneOffset; | ||
import java.util.HashSet; | ||
import java.util.Objects; | ||
import java.util.concurrent.CompletableFuture; | ||
import reactor.core.publisher.Mono; | ||
|
||
/** | ||
* | ||
* An implementation of ServiceClientCredentials that supports automatic bearer token refresh. | ||
* | ||
* An AAD credential that acquires a token with a client secret for an AAD application. | ||
*/ | ||
class KeyVaultCredential extends KeyVaultCredentials { | ||
|
||
SQLServerKeyVaultAuthenticationCallback authenticationCallback = null; | ||
String clientId = null; | ||
String clientKey = null; | ||
String accessToken = null; | ||
@Immutable | ||
class KeyVaultCredential implements TokenCredential { | ||
private final ClientLogger logger = new ClientLogger(KeyVaultCredential.class); | ||
private final String clientId; | ||
private final String clientSecret; | ||
private String authorization; | ||
private ConfidentialClientApplication confidentialClientApplication; | ||
|
||
KeyVaultCredential(String clientId) throws SQLServerException { | ||
/** | ||
* Creates a KeyVaultCredential with the given identity client options. | ||
* | ||
* @param clientId the client ID of the application | ||
* @param clientSecret the secret value of the AAD application. | ||
*/ | ||
KeyVaultCredential(String clientId, String clientSecret) { | ||
Objects.requireNonNull(clientSecret, "'clientSecret' cannot be null."); | ||
Objects.requireNonNull(clientSecret, "'clientId' cannot be null."); | ||
this.clientId = clientId; | ||
this.clientSecret = clientSecret; | ||
} | ||
|
||
KeyVaultCredential() {} | ||
|
||
KeyVaultCredential(String clientId, String clientKey) { | ||
this.clientId = clientId; | ||
this.clientKey = clientKey; | ||
@Override | ||
public Mono<AccessToken> getToken(TokenRequestContext request) { | ||
return authenticateWithConfidentialClientCache(request) | ||
.onErrorResume(t -> Mono.empty()) | ||
.switchIfEmpty(Mono.defer(() -> authenticateWithConfidentialClient(request))); | ||
} | ||
|
||
KeyVaultCredential(SQLServerKeyVaultAuthenticationCallback authenticationCallback) { | ||
this.authenticationCallback = authenticationCallback; | ||
public KeyVaultCredential setAuthorization(String authorization) { | ||
if (this.authorization != null && this.authorization.equals(authorization)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. our coding standard: null != this.authorization as it prevents accidental assignment |
||
return this; | ||
} | ||
this.authorization = authorization; | ||
confidentialClientApplication = getConfidentialClientApplication(); | ||
return this; | ||
} | ||
|
||
public String doAuthenticate(String authorization, String resource, String scope) { | ||
String accessToken = null; | ||
if (null == authenticationCallback) { | ||
if (null == clientKey) { | ||
try { | ||
SqlFedAuthToken token = SQLServerSecurityUtility.getMSIAuthToken(resource, clientId); | ||
accessToken = (null != token) ? token.accessToken : null; | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} else { | ||
AuthenticationResult token = getAccessTokenFromClientCredentials(authorization, resource, clientId, | ||
clientKey); | ||
accessToken = token.getAccessToken(); | ||
} | ||
private ConfidentialClientApplication getConfidentialClientApplication() { | ||
if (clientId == null) { | ||
throw logger.logExceptionAsError(new IllegalArgumentException( | ||
"A non-null value for client ID must be provided for user authentication.")); | ||
} | ||
|
||
if (authorization == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. null == authorization (see above) |
||
throw logger.logExceptionAsError(new IllegalArgumentException( | ||
"A non-null value for authorization must be provided for user authentication.")); | ||
} | ||
|
||
IClientCredential credential; | ||
if (clientSecret != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. null != clientSecret (see above) |
||
credential = ClientCredentialFactory.create(clientSecret); | ||
} else { | ||
accessToken = authenticationCallback.getAccessToken(authorization, resource, scope); | ||
throw logger.logExceptionAsError( | ||
new IllegalArgumentException("Must provide client secret.")); | ||
} | ||
ConfidentialClientApplication.Builder applicationBuilder = | ||
ConfidentialClientApplication.builder(clientId, credential); | ||
try { | ||
applicationBuilder = applicationBuilder.authority(authorization); | ||
} catch (MalformedURLException e) { | ||
throw logger.logExceptionAsWarning(new IllegalStateException(e)); | ||
} | ||
return accessToken; | ||
return applicationBuilder.build(); | ||
} | ||
|
||
private static AuthenticationResult getAccessTokenFromClientCredentials(String authorization, String resource, | ||
String clientId, String clientKey) { | ||
AuthenticationContext context = null; | ||
AuthenticationResult result = null; | ||
ExecutorService service = null; | ||
try { | ||
service = Executors.newFixedThreadPool(1); | ||
context = new AuthenticationContext(authorization, false, service); | ||
ClientCredential credentials = new ClientCredential(clientId, clientKey); | ||
Future<AuthenticationResult> future = context.acquireToken(resource, credentials, null); | ||
result = future.get(); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} finally { | ||
if (null != service) { | ||
service.shutdown(); | ||
private Mono<AccessToken> authenticateWithConfidentialClientCache(TokenRequestContext request) { | ||
return Mono.fromFuture(() -> { | ||
SilentParameters.SilentParametersBuilder parametersBuilder = SilentParameters | ||
.builder(new HashSet<>(request.getScopes())); | ||
try { | ||
return confidentialClientApplication.acquireTokenSilently(parametersBuilder.build()); | ||
} catch (MalformedURLException e) { | ||
return getFailedCompletableFuture(logger.logExceptionAsError(new RuntimeException(e))); | ||
} | ||
} | ||
}).map(ar -> new AccessToken(ar.accessToken(), | ||
OffsetDateTime.ofInstant(ar.expiresOnDate().toInstant(), ZoneOffset.UTC))) | ||
.filter(t -> !t.isExpired()); | ||
} | ||
|
||
if (null == result) { | ||
throw new RuntimeException("authentication result was null"); | ||
} | ||
return result; | ||
private CompletableFuture<IAuthenticationResult> getFailedCompletableFuture(Exception e) { | ||
CompletableFuture<IAuthenticationResult> completableFuture = new CompletableFuture<>(); | ||
completableFuture.completeExceptionally(e); | ||
return completableFuture; | ||
} | ||
|
||
private Mono<AccessToken> authenticateWithConfidentialClient(TokenRequestContext request) { | ||
return Mono.fromFuture(() -> confidentialClientApplication | ||
.acquireToken(ClientCredentialParameters.builder(new HashSet<>(request.getScopes())).build())) | ||
.map(ar -> new AccessToken(ar.accessToken(), | ||
OffsetDateTime.ofInstant(ar.expiresOnDate().toInstant(), ZoneOffset.UTC))); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tenantID not necessary anymore