• 【Azure Developer】使用 Microsoft Authentication Libraries (MSAL) 如何来获取Token呢 (通过用户名和密码方式获取Access Token)


    问题描述

    在上一篇博文《【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

     

  • 相关阅读:
    在Mac上一键安装Mysql(解决所有安装问题)
    【设计模式-2】策略模式 - 避免冗余的if-else判断
    Git命令总结
    linux用户空间向内核空间通信之使用proc接口和使用WARN_ON和BUG_ON调试内核代码
    Python语言基本语法元素
    低代码平台:打破数据孤岛的利器,推动企业数字化转型
    腾讯云16核服务器配置大全_16核CPU型号性能测评
    python如何利用算法解决业务上的【分单问题】
    7-MySQL函数
    云链商城连锁门店新零售O20系统以零售商城
  • 原文地址:https://www.cnblogs.com/lulight/p/16226211.html