Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. See License.txt in the project root for
license information.
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/
package com.microsoft.azure.credentials;
import com.google.common.io.BaseEncoding;
import com.microsoft.aad.adal4j.AsymmetricKeyCredential;
import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;
import com.microsoft.azure.AzureEnvironment;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Token based credentials for use with a REST Service Client.
/**
* Token based credentials for use with a REST Service Client.
*/
public class ApplicationTokenCredentials extends AzureTokenCredentials {
A mapping from resource endpoint to its cached access token. /** A mapping from resource endpoint to its cached access token. */
private final ConcurrentHashMap<String, AuthenticationResult> tokens;
A mapping from resource endpoint to its current authentication locks. /** A mapping from resource endpoint to its current authentication locks. */
private final ConcurrentHashMap<String, ReentrantLock> authenticationLocks;
The active directory application client id. /** The active directory application client id. */
private final String clientId;
The authentication secret for the application. /** The authentication secret for the application. */
private String clientSecret;
The PKCS12 certificate byte array. /** The PKCS12 certificate byte array. */
private byte[] clientCertificate;
The certificate password. /** The certificate password. */
private String clientCertificatePassword;
The timeout for authentication calls. /** The timeout for authentication calls. */
private long timeoutInSeconds = 60;
Initializes a new instance of the ApplicationTokenCredentials.
Params: - clientId – the active directory application client id. Also known as Application Id which Identifies the application that is using the token.
- domain – the domain or tenant id containing this application.
- secret – the authentication secret for the application.
- environment – the Azure environment to authenticate with.
If null is provided, AzureEnvironment.AZURE will be used.
/**
* Initializes a new instance of the ApplicationTokenCredentials.
*
* @param clientId the active directory application client id. Also known as Application Id which Identifies the application that is using the token.
* @param domain the domain or tenant id containing this application.
* @param secret the authentication secret for the application.
* @param environment the Azure environment to authenticate with.
* If null is provided, AzureEnvironment.AZURE will be used.
*/
public ApplicationTokenCredentials(String clientId, String domain, String secret, AzureEnvironment environment) {
super(environment, domain); // defer token acquisition
this.clientId = clientId;
this.clientSecret = secret;
this.tokens = new ConcurrentHashMap<>();
this.authenticationLocks = new ConcurrentHashMap<>();
}
Initializes a new instance of the ApplicationTokenCredentials.
Params: - clientId – the active directory application client id. Also known as Application Id which Identifies the application that is using the token.
- domain – the domain or tenant id containing this application.
- certificate – the PKCS12 certificate file content
- password – the password to the certificate file
- environment – the Azure environment to authenticate with.
If null is provided, AzureEnvironment.AZURE will be used.
/**
* Initializes a new instance of the ApplicationTokenCredentials.
*
* @param clientId the active directory application client id. Also known as Application Id which Identifies the application that is using the token.
* @param domain the domain or tenant id containing this application.
* @param certificate the PKCS12 certificate file content
* @param password the password to the certificate file
* @param environment the Azure environment to authenticate with.
* If null is provided, AzureEnvironment.AZURE will be used.
*/
public ApplicationTokenCredentials(String clientId, String domain, byte[] certificate, String password, AzureEnvironment environment) {
super(environment, domain);
this.clientId = clientId;
this.clientCertificate = certificate;
this.clientCertificatePassword = password;
this.tokens = new ConcurrentHashMap<>();
this.authenticationLocks = new ConcurrentHashMap<>();
}
Initializes the credentials based on the provided credentials file.
Params: - credentialsFile – A file with credentials, using the standard Java properties format. and the following keys: subscription=<subscription-id> tenant=<tenant-id> client=<client-id> key=<client-key> managementURI=<management-URI> baseURL=<base-URL> authURL=<authentication-URL> or a JSON format and the following keys { "clientId": "<client-id>", "clientSecret": "<client-key>", "subscriptionId": "<subscription-id>", "tenantId": "<tenant-id>", } and any custom endpoints listed in
AzureEnvironment
.
Throws: - IOException – exception thrown from file access errors.
Returns: The credentials based on the file.
/**
* Initializes the credentials based on the provided credentials file.
*
* @param credentialsFile A file with credentials, using the standard Java properties format.
* and the following keys:
* subscription=<subscription-id>
* tenant=<tenant-id>
* client=<client-id>
* key=<client-key>
* managementURI=<management-URI>
* baseURL=<base-URL>
* authURL=<authentication-URL>
* or a JSON format and the following keys
* {
* "clientId": "<client-id>",
* "clientSecret": "<client-key>",
* "subscriptionId": "<subscription-id>",
* "tenantId": "<tenant-id>",
* }
* and any custom endpoints listed in {@link AzureEnvironment}.
*
* @return The credentials based on the file.
* @throws IOException exception thrown from file access errors.
*/
public static ApplicationTokenCredentials fromFile(File credentialsFile) throws IOException {
return AuthFile.parse(credentialsFile).generateCredentials();
}
Gets the active directory application client id. Also known as Application Id which Identifies the application that is using the token.
Returns: the active directory application client id.
/**
* Gets the active directory application client id. Also known as Application Id which Identifies the application that is using the token.
*
* @return the active directory application client id.
*/
public String clientId() {
return clientId;
}
public String clientSecret() {
return clientSecret;
}
public byte[] clientCertificate() {
return clientCertificate;
}
public String clientCertificatePassword() {
return clientCertificatePassword;
}
Gets the timeout for AAD authentication calls. Default is 60 seconds.
Returns: the timeout in seconds.
/**
* Gets the timeout for AAD authentication calls. Default is 60 seconds.
*
* @return the timeout in seconds.
*/
public long timeoutInSeconds() {
return timeoutInSeconds;
}
Sets the timeout for AAD authentication calls. Default is 60 seconds.
Params: - timeoutInSeconds – the timeout in seconds.
Returns: the modified ApplicationTokenCredentials instance
/**
* Sets the timeout for AAD authentication calls. Default is 60 seconds.
*
* @param timeoutInSeconds the timeout in seconds.
* @return the modified ApplicationTokenCredentials instance
*/
public ApplicationTokenCredentials withTimeoutInSeconds(long timeoutInSeconds) {
this.timeoutInSeconds = timeoutInSeconds;
return this;
}
@Override
public String getToken(String resource) throws IOException {
AuthenticationResult authenticationResult = tokens.get(resource);
if (authenticationResult == null || authenticationResult.getExpiresOnDate().before(new Date())) {
ReentrantLock lock;
synchronized (authenticationLocks) {
lock = authenticationLocks.get(resource);
if (lock == null) {
lock = new ReentrantLock();
authenticationLocks.put(resource, lock);
}
}
lock.lock();
try {
authenticationResult = tokens.get(resource);
if (authenticationResult == null || authenticationResult.getExpiresOnDate().before(new Date())) {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
authenticationResult = acquireAccessToken(resource, executor).get(timeoutInSeconds(), TimeUnit.SECONDS);
tokens.put(resource, authenticationResult);
} finally {
executor.shutdown();
}
}
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
} finally {
lock.unlock();
}
}
return authenticationResult.getAccessToken();
}
Future<AuthenticationResult> acquireAccessToken(String resource, ExecutorService executor) throws IOException {
String authorityUrl = this.environment().activeDirectoryEndpoint() + this.domain();
AuthenticationContext context = new AuthenticationContext(authorityUrl, false, executor);
if (proxy() != null) {
context.setProxy(proxy());
}
if (sslSocketFactory() != null) {
context.setSslSocketFactory(sslSocketFactory());
}
try {
if (clientSecret() != null) {
return context.acquireToken(
resource,
new ClientCredential(clientId(), clientSecret()),
null);
} else if (clientCertificate() != null && clientCertificatePassword() != null) {
return context.acquireToken(
resource,
AsymmetricKeyCredential.create(clientId(), new ByteArrayInputStream(clientCertificate()), clientCertificatePassword()),
null);
} else if (clientCertificate() != null) {
return context.acquireToken(
resource,
AsymmetricKeyCredential.create(clientId(), privateKeyFromPem(new String(clientCertificate())), publicKeyFromPem(new String(clientCertificate()))),
null);
}
throw new AuthenticationException("Please provide either a non-null secret or a non-null certificate.");
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
}
}
static PrivateKey privateKeyFromPem(String pem) {
Pattern pattern = Pattern.compile("(?s)-----BEGIN PRIVATE KEY-----.*-----END PRIVATE KEY-----");
Matcher matcher = pattern.matcher(pem);
matcher.find();
String base64 = matcher.group()
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "")
.replace("\r", "");
byte[] key = BaseEncoding.base64().decode(base64);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
}
static X509Certificate publicKeyFromPem(String pem) {
Pattern pattern = Pattern.compile("(?s)-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----");
Matcher matcher = pattern.matcher(pem);
matcher.find();
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
InputStream stream = new ByteArrayInputStream(matcher.group().getBytes());
return (X509Certificate) factory.generateCertificate(stream);
} catch (CertificateException e) {
throw new RuntimeException(e);
}
}
}