問題描述
在上一篇博文《【Azure Developer】使用 adal4j(Azure Active Directory authentication library for Java)如何來獲取Token呢 (通過使用者名稱和密碼方式獲取Access Token)》中,介紹了使用ADAL4J SDK獲取Access Token。而ADAL4J是非常舊的SDK,最新的SDK名稱為 MSAL4J (Microsoft Authentication Libraries),原來的AcquireToken的函式與現在的方式變動較大,不能直接修改POM.XML中依賴的方式來解決問題。
ADAL4J的acquireToken方法:
/** * Acquires a security token from the authority using a Refresh Token * previously received. * * @param clientId * Name or ID of the client requesting the token. * @param resource * Identifier of the target resource that is the recipient of the * requested token. If null, token is requested for the same * resource refresh token was originally issued for. If passed, * resource should match the original resource used to acquire * refresh token unless token service supports refresh token for * multiple resources. * @param username * Username of the managed or federated user. * @param password * Password of the managed or federated user. * @param callback * optional callback object for non-blocking execution. * @return A {@link Future} object representing the * {@link AuthenticationResult} of the call. It contains Access * Token, Refresh Token and the Access Token's expiration time. */ public Future<AuthenticationResult> acquireToken(final String resource, final String clientId, final String username, final String password, final AuthenticationCallback callback) { if (StringHelper.isBlank(resource)) { throw new IllegalArgumentException("resource is null or empty"); } if (StringHelper.isBlank(clientId)) { throw new IllegalArgumentException("clientId is null or empty"); } if (StringHelper.isBlank(username)) { throw new IllegalArgumentException("username is null or empty"); } if (StringHelper.isBlank(password)) { throw new IllegalArgumentException("password is null or empty"); } return this.acquireToken(new AdalAuthorizatonGrant( new ResourceOwnerPasswordCredentialsGrant(username, new Secret( password)), resource), new ClientAuthenticationPost( ClientAuthenticationMethod.NONE, new ClientID(clientId)), callback); }
MSAL4J的acquireToken方法:
public CompletableFuture<IAuthenticationResult> acquireToken(UserNamePasswordParameters parameters) { validateNotNull("parameters", parameters); UserNamePasswordRequest userNamePasswordRequest = new UserNamePasswordRequest(parameters, this, createRequestContext(PublicApi.ACQUIRE_TOKEN_BY_USERNAME_PASSWORD)); return this.executeRequest(userNamePasswordRequest); }
/** * Builder for UserNameParameters * @param scopes scopes application is requesting access to * @param username username of the account * @param password char array containing credentials for the username * @return builder object that can be used to construct UserNameParameters */ public static UserNamePasswordParametersBuilder builder(Set<String> scopes, String username, char[] password) { validateNotEmpty("scopes", scopes); validateNotBlank("username", username); validateNotEmpty("password", password); return builder().scopes(scopes).username(username).password(password); }
那麼,通過MSAL4J SDK,如何使用使用者名稱,密碼來獲取到Access Token呢?
問題解答
和使用ADAL4J一樣,都是需要使用Azure AD中的使用者,以及一個Azure AD 註冊應用(此應用需要開啟“Allow public client flows”功能),開啟步驟見博文《【Azure Developer】使用 adal4j(Azure Active Directory authentication library for Java)如何來獲取Token呢 (通過使用者名稱和密碼方式獲取Access Token)》中。
示例程式碼
package com.example; import java.util.Collections; import java.util.Set; import com.microsoft.aad.msal4j.*; /** * Hello world! * */ public class App { private static String authority = "https://login.chinacloudapi.cn/<your tenant id>/"; private static Set<String> scope = Collections.singleton("https://ossrdbms-aad.database.chinacloudapi.cn/.default"); private static String clientId ="Azure AD Application(Client) ID"; private static String username ="AAD USER @XXXX.partner.onmschina.cn"; private static String password = "USER PASSWORD"; public static void main(String[] args) throws Exception { System.out.println("Hello World!"); System.out.println("Hello App to get Token by Username & Password...."); PublicClientApplication pca = PublicClientApplication.builder(clientId) .authority(authority) .build(); //Get list of accounts from the application's token cache, and search them for the configured username //getAccounts() will be empty on this first call, as accounts are added to the cache when acquiring a token Set<IAccount> accountsInCache = pca.getAccounts().join(); IAccount account = getAccountByUsername(accountsInCache, username); //Attempt to acquire token when user's account is not in the application's token cache IAuthenticationResult result = acquireTokenUsernamePassword(pca, scope, account, username, password); System.out.println("Account username: " + result.account().username()); System.out.println("Access token: " + result.accessToken()); System.out.println("Id token: " + result.idToken()); System.out.println(); accountsInCache = pca.getAccounts().join(); account = getAccountByUsername(accountsInCache, username); //Attempt to acquire token again, now that the user's account and a token are in the application's token cache result = acquireTokenUsernamePassword(pca, scope, account, username, password); System.out.println("Account username: " + result.account().username()); System.out.println("Access token: " + result.accessToken()); System.out.println("Id token: " + result.idToken()); } private static IAuthenticationResult acquireTokenUsernamePassword(PublicClientApplication pca, Set<String> scope, IAccount account, String username, String password) throws Exception { IAuthenticationResult result; try { SilentParameters silentParameters = SilentParameters .builder(scope) .account(account) .build(); // Try to acquire token silently. This will fail on the first acquireTokenUsernamePassword() call // because the token cache does not have any data for the user you are trying to acquire a token for result = pca.acquireTokenSilently(silentParameters).join(); System.out.println("==acquireTokenSilently call succeeded"); } catch (Exception ex) { if (ex.getCause() instanceof MsalException) { System.out.println("==acquireTokenSilently call failed: " + ex.getCause()); UserNamePasswordParameters parameters = UserNamePasswordParameters .builder(scope, username, password.toCharArray()) .build(); // Try to acquire a token via username/password. If successful, you should see // the token and account information printed out to console result = pca.acquireToken(parameters).join(); System.out.println("==username/password flow succeeded"); } else { // Handle other exceptions accordingly throw ex; } } return result; } /** * Helper function to return an account from a given set of accounts based on the given username, * or return null if no accounts in the set match */ private static IAccount getAccountByUsername(Set<IAccount> accounts, String username) { if (accounts.isEmpty()) { System.out.println("==No accounts in cache"); } else { System.out.println("==Accounts in cache: " + accounts.size()); for (IAccount account : accounts) { if (account.username().equals(username)) { return account; } } } return null; } }
在POM.XML檔案中新增依賴Package:
<dependency> <groupId>com.microsoft.azure</groupId> <artifactId>msal4j</artifactId> <version>1.0.0</version> </dependency>
注意:以上程式碼最關鍵的部分就是 UserNamePasswordParameters 的設定。scope 也是需要根據Token的資源而變動,如以上示例程式碼中使用的 https://ossrdbms-aad.database.chinacloudapi.cn/.default , 而在adal4j的示例中,resource的值為:https://microsoftgraph.chinacloudapi.cn/。
執行效果為
附錄一:遇見 Administrator has not consented the application的問題
錯誤訊息:
Caused by: com.microsoft.aad.adal4j.AuthenticationException:
{"error_description":
"AADSTS65001: The user or administrator has not consented to use the application with ID 'xxxxxxxx-xxxx-4fa8-xxxx-xxxxxxxxxxxx' named 'xxxxtest01'.
Send an interactive authorization request for this user and resource.\r\n
Trace ID:xxxxxx-xxx-xxx----xxxxxx\r\n
Correlation ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n
Timestamp: 2022-05-05 08:16:16Z",
"error":"invalid_grant"}
此類問題的解決方法為:
1)進入Azure AD頁面,找到當前User的登入日誌資訊(Sign-in logs),檢視失敗的記錄,在詳細記錄中,檢視Status為 Interrupted的記錄,找到 Resource 和Application 資訊。在第二步中使用這兩個資訊。
2)回到Azure AD的註冊應用頁面,找到第一步中的Applicaiton,然後進入API Permission頁面。在API Permission頁面中點選“Add a Permission”,然後再“APIs my Organization uses”的文字框中輸入“Azure OSSRDBMS Database”進行搜尋,然後選中它,並賦予“Delegated Permissions”許可權。如下圖:
參考資料
Java console application letting users sign-in with username/password and call Microsoft Graph API:https://github.com/Azure-Samples/ms-identity-java-desktop/tree/da27a1af6064d5e833e645e5040a5120a0c2698f/Username-Password-Flow
Microsoft identity platform and OAuth 2.0 Resource Owner Password Credentials:https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
使用 adal4j(Azure Active Directory authentication library for Java)如何來獲取Token呢 (通過使用者名稱和密碼方式獲取Access Token) : https://www.cnblogs.com/lulight/p/16212275.html