diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java
index c4a2b676491ae..4f360b5cd41dc 100644
--- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java
+++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java
@@ -676,7 +676,9 @@ public AccessTokenProvider getTokenProvider() throws TokenAccessProviderExceptio
String authEndpoint = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_CLIENT_ENDPOINT);
String username = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_USER_NAME);
String password = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_USER_PASSWORD);
- tokenProvider = new UserPasswordTokenProvider(authEndpoint, username, password);
+ String clientId = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_CLIENT_ID);
+ String clientSecret = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_CLIENT_SECRET);
+ tokenProvider = new UserPasswordTokenProvider(authEndpoint, username, password, clientId, clientSecret);
LOG.trace("UserPasswordTokenProvider initialized");
} else if (tokenProviderClass == MsiTokenProvider.class) {
String authEndpoint = getTrimmedPasswordString(
diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/AzureADAuthenticator.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/AzureADAuthenticator.java
index 49f90feb22e95..1b9f732dca9f1 100644
--- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/AzureADAuthenticator.java
+++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/AzureADAuthenticator.java
@@ -154,6 +154,53 @@ public static AzureADToken getTokenFromMsi(final String authEndpoint,
return getTokenCall(authEndpoint, qp.serialize(), headers, "GET", true);
}
+
+ /**
+ * gets Azure Active Directory token using the user's username and password. This only
+ * works if the identity can be authenticated directly by microsoftonline.com. It will likely
+ * not work if the domain is federated and/or multi-factor authentication or other form of
+ * strong authentication is configured for the user.
+ *
+ * @param authEndpoint the OAuth 2.0 token endpoint associated
+ * with the user's directory (obtain from
+ * Active Directory configuration)
+ * @param username the user name of the user
+ * @param password the password of the user
+ * @param clientId the client ID (GUID) of the client web app
+ * obtained from Azure Active Directory configuration
+ * @param clientSecret (optional) the secret key of the client web app
+ * If the app is a confidential client, then it must be included
+ * @return {@link AzureADToken} obtained using the creds
+ * @throws IOException throws IOException if there is a failure in connecting to Azure AD
+ */
+ public static AzureADToken getTokenUsingUserCreds(String authEndpoint,
+ String username, String password, String clientId,
+ String clientSecret) throws IOException {
+ Preconditions.checkNotNull(authEndpoint, "authEndpoint");
+ Preconditions.checkNotNull(username, "username");
+ Preconditions.checkNotNull(password, "password");
+ Preconditions.checkNotNull(clientId, "clientId");
+ boolean isVersion2AuthenticationEndpoint = authEndpoint.contains("/oauth2/v2.0/");
+
+ QueryParams qp = new QueryParams();
+ if (isVersion2AuthenticationEndpoint) {
+ qp.add("scope", SCOPE);
+ } else {
+ qp.add("resource", RESOURCE_NAME);
+ }
+ qp.add("grant_type", "password");
+ qp.add("client_id", clientId);
+ qp.add("username",username);
+ qp.add("password",password);
+
+ if (clientSecret != null && clientSecret.length() > 0) {
+ qp.add("client_secret", clientSecret);
+ }
+
+ LOG.debug("AADToken: starting to fetch token using username and password");
+ return getTokenCall(authEndpoint, qp.serialize(), null, null);
+ }
+
/**
* Gets Azure Active Directory token using refresh token.
*
diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/UserPasswordTokenProvider.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/UserPasswordTokenProvider.java
index 3d9d8b865a059..fd5fa20f62732 100644
--- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/UserPasswordTokenProvider.java
+++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/UserPasswordTokenProvider.java
@@ -35,22 +35,30 @@ public class UserPasswordTokenProvider extends AccessTokenProvider {
private final String password;
+ private final String clientId;
+
+ private final String clientSecret;
+
private static final Logger LOG = LoggerFactory.getLogger(AccessTokenProvider.class);
public UserPasswordTokenProvider(final String authEndpoint,
- final String username, final String password) {
+ final String username, final String password,
+ final String clientId, final String clientSecret) {
Preconditions.checkNotNull(authEndpoint, "authEndpoint");
Preconditions.checkNotNull(username, "username");
Preconditions.checkNotNull(password, "password");
+ Preconditions.checkNotNull(clientId, "clientId");
this.authEndpoint = authEndpoint;
this.username = username;
this.password = password;
+ this.clientId = clientId;
+ this.clientSecret = clientSecret;
}
@Override
protected AzureADToken refreshToken() throws IOException {
LOG.debug("AADToken: refreshing user-password based token");
- return AzureADAuthenticator.getTokenUsingClientCreds(authEndpoint, username, password);
+ return AzureADAuthenticator.getTokenUsingUserCreds(authEndpoint, username, password, clientId, clientSecret);
}
}
diff --git a/hadoop-tools/hadoop-azure/src/site/markdown/abfs.md b/hadoop-tools/hadoop-azure/src/site/markdown/abfs.md
index 79b897b6bd25c..2c44e2adcbb12 100644
--- a/hadoop-tools/hadoop-azure/src/site/markdown/abfs.md
+++ b/hadoop-tools/hadoop-azure/src/site/markdown/abfs.md
@@ -467,6 +467,20 @@ An OAuth 2.0 endpoint, username and password are provided in the configuration/J
password for account
+
+ fs.azure.account.oauth2.client.id
+
+
+ Client ID
+
+
+
+ fs.azure.account.oauth2.client.secret
+
+
+ Optional Secret
+
+
```
### OAuth 2.0: Refresh Token