教程:在 Android (Kotlin) 移动应用中登录用户

这是本教程系列中的第三个教程,介绍如何使用 Microsoft Entra 外部 ID 登录用户。

在本教程中,你将:

  • 登录用户
  • 注销用户

先决条件

教程:准备 Android 应用进行身份验证

登录用户

可通过两个主要选项使用适用于 Android 的 Microsoft 身份验证库 (MSAL) 登录用户:以交互方式或无提示方式获取令牌。

  1. 若要以交互方式登录用户,请使用以下代码:

        private fun acquireTokenInteractively() {
        binding.txtLog.text = ""
    
        if (account != null) {
            Toast.makeText(this, "An account is already signed in.", Toast.LENGTH_SHORT).show()
            return
        }
    
        /* Extracts a scope array from text, i.e. from "User.Read User.ReadWrite" to ["user.read", "user.readwrite"] */
        val scopes = scopes.lowercase().split(" ")
        val parameters = AcquireTokenParameters.Builder()
            .startAuthorizationFromActivity(this@MainActivity)
            .withScopes(scopes)
            .withCallback(getAuthInteractiveCallback())
            .build()
    
        authClient.acquireToken(parameters)
    }
    

    该代码会启动使用适用于 Android 的 MSAL 以交互方式获取令牌的过程。 它首先会清除文本日志字段。 然后,它会检查是否有已登录帐户,如果有,会显示一条 Toast 消息,指示帐户已登录,然后返回。

    接下来,它会从文本输入中提取范围并将其转换为小写,然后再将它们拆分为数组。 使用这些范围,它会生成用于获取令牌的参数,包括从当前活动启动授权过程并指定回叫。 最后,它使用构造的参数在身份验证客户端上调用 acquireToken(),以启动令牌获取过程。

    在代码中指定回叫的位置,使用一个名为 getAuthInteractiveCallback() 的函数。 该函数应具有以下代码:

    private fun getAuthInteractiveCallback(): AuthenticationCallback {
        return object : AuthenticationCallback {
    
            override fun onSuccess(authenticationResult: IAuthenticationResult) {
                /* Successfully got a token, use it to call a protected resource - Web API */
                Log.d(TAG, "Successfully authenticated")
                Log.d(TAG, "ID Token: " + authenticationResult.account.claims?.get("id_token"))
                Log.d(TAG, "Claims: " + authenticationResult.account.claims
    
                /* Reload account asynchronously to get the up-to-date list. */
                CoroutineScope(Dispatchers.Main).launch {
                    accessToken = authenticationResult.accessToken
                    getAccount()
    
                    binding.txtLog.text = getString(R.string.log_token_interactive) +  accessToken
                }
            }
    
            override fun onError(exception: MsalException) {
                /* Failed to acquireToken */
                Log.d(TAG, "Authentication failed: $exception")
    
                accessToken = null
                binding.txtLog.text = getString(R.string.exception_authentication) + exception
    
                if (exception is MsalClientException) {
                    /* Exception inside MSAL, more info inside MsalError.java */
                } else if (exception is MsalServiceException) {
                    /* Exception when communicating with the STS, likely config issue */
                }
            }
    
            override fun onCancel() {
                /* User canceled the authentication */
                Log.d(TAG, "User cancelled login.");
            }
        }
    }
    

    该代码片段定义了函数 getAuthInteractiveCallback,该函数会返回 AuthenticationCallback 的实例。 在此函数中,将创建一个实现 AuthenticationCallback 接口的匿名类。

    身份验证成功 (onSuccess) 后,它将记录成功的身份验证、检索 ID 令牌和声明、使用 CoroutineScope 异步更新访问令牌,并使用新的访问令牌更新 UI。 该代码会从 authenticationResult 检索 ID 令牌并进行记录。 令牌中的声明包含有关用户的信息,例如其名称、电子邮件或其他配置文件信息。 可以通过访问 authenticationResult.account.claims 检索与当前帐户关联的声明。

    如果存在身份验证错误 (onError),则会记录错误、清除访问令牌、使用错误消息更新 UI,并为 MsalClientExceptionMsalServiceException 提供更具体的处理。 如果用户取消身份验证 (onCancel),则会记录取消。

    确保包含 import 语句。 Android Studio 应该会自动为你包含 import 语句。

  2. 若要以无提示方式登录用户,请使用以下代码:

        private fun acquireTokenSilently() {
        binding.txtLog.text = ""
    
        if (account == null) {
            Toast.makeText(this, "No account available", Toast.LENGTH_SHORT).show()
            return
        }
    
        /* Extracts a scope array from text, i.e. from "User.Read User.ReadWrite" to ["user.read", "user.readwrite"] */
        val scopes = scopes.lowercase().split(" ")
        val parameters = AcquireTokenSilentParameters.Builder()
            .forAccount(account)
            .fromAuthority(account!!.authority)
            .withScopes(scopes)
            .forceRefresh(false)
            .withCallback(getAuthSilentCallback())
            .build()
    
        authClient.acquireTokenSilentAsync(parameters)
    }
    

    该代码会启动以无提示方式获取令牌的过程。 它首先会清除文本日志。 然后,它会检查是否有可用帐户;如果没有,将显示一条 toast 消息,指示没有可用账户,然后退出。 接下来,它会从文本输入中提取范围,将其转换为小写,然后再将它们拆分为数组。

    使用这些范围,它会构造以无提示方式获取令牌的参数,指定帐户、颁发机构、范围和回叫。 最后,它会使用构造的参数在身份验证客户端上异步触发 acquireTokenSilentAsync(),启动无提示的令牌获取过程。

    在代码中指定回叫的位置,使用一个名为 getAuthSilentCallback() 的函数。 该函数应具有以下代码:

    private fun getAuthSilentCallback(): SilentAuthenticationCallback {
        return object : SilentAuthenticationCallback {
            override fun onSuccess(authenticationResult: IAuthenticationResult?) {
                Log.d(TAG, "Successfully authenticated")
    
                /* Display Access Token */
                accessToken = authenticationResult?.accessToken
                binding.txtLog.text = getString(R.string.log_token_silent) + accessToken
            }
    
            override fun onError(exception: MsalException?) {
                /* Failed to acquireToken */
                Log.d(TAG, "Authentication failed: $exception")
    
                accessToken = null
                binding.txtLog.text = getString(R.string.exception_authentication) + exception
    
                when (exception) {
                    is MsalClientException -> {
                        /* Exception inside MSAL, more info inside MsalError.java */
                    }
                    is MsalServiceException -> {
                        /* Exception when communicating with the STS, likely config issue */
                    }
                    is MsalUiRequiredException -> {
                        /* Tokens expired or no session, retry with interactive */
                    }
                }
            }
    
        }
    }
    

    该代码定义了用于无提示身份验证的回叫。 它可实现 SilentAuthenticationCallback 接口,重写两种方法。 在 onSuccess 方法中,它会记录成功的身份验证并显示访问令牌。

    onError 方法中,它会记录身份验证失败,处理不同类型的异常,例如 MsalClientExceptionMsalServiceException,并根据需要建议使用交互式身份验证重试。

    确保包含 import 语句。 Android Studio 应该会自动为你包含 import 语句。

注销

若要使用适用于 Android 的 MSAL 从 Android 应用注销用户,请使用以下代码:

private fun removeAccount() {
    binding.userName.text = ""
    binding.txtLog.text = ""

    authClient.signOut(signOutCallback())
}

该代码会从应用程序中移除帐户。 它会清除显示的用户名和文本日志。 然后,它会使用身份验证客户端触发注销过程,并指定注销回叫来处理注销操作的完成。

在代码中指定回叫的位置,使用一个名为 signOutCallback() 的函数。 该函数应具有以下代码:

private fun signOutCallback(): ISingleAccountPublicClientApplication.SignOutCallback {
    return object : ISingleAccountPublicClientApplication.SignOutCallback {
        override fun onSignOut() {
            account = null
            updateUI(account)
        }

        override fun onError(exception: MsalException) {
            binding.txtLog.text = getString(R.string.exception_remove_account) + exception
        }
    }
}

该代码为公共客户端应用程序中的单个帐户定义注销回叫。 它可实现 ISingleAccountPublicClientApplication.SignOutCallback 接口,重写两种方法。

onSignOut 方法中,它会使当前帐户无效,并相应地更新用户界面。 在 onError 方法中,它会记录注销过程中发生的任何错误,并使用相应的异常消息更新文本日志。

确保包含 import 语句。 Android Studio 应该会自动为你包含 import 语句。

后续步骤

教程:在 Android (Kotlin) 应用中调用受保护的 Web API