Xamarin Forms Android - 使用 MSAL 库进行部署时的身份验证失败

Xamarin Forms Android - Authentication Failure on Deployment using MSAL Library

开发信息 - Xamarin Forms Mobile 应用程序,利用 MSAL 库版本 4.35.0 对 Azure AD 进行身份验证,并使用 Microsoft Authenticator 的代理身份验证流程。这是使用 C# 和 .Net 5 在 Visual Studio 2019 中编码的。

问题 - 在 Android 模拟器中一切正常,但一旦部署到实际设备,使用公司门户 (Intune),身份验证部分失败并显示消息:

Authentication Error [Android broker] The broker redirect URI is incorrect, it should be msauth://com.xxxxxx.xxxxxxx/xxxxxxxxxxxxxx Please visit https://aka.ms/Brokered-Authentication-for-Android for more details

我将 Azure 门户中的重定向 uri 与错误消息中显示的重定向 uri 进行了比较,但它们不匹配,我不知道它是从哪里获取此重定向 uri 值的?代码库中的所有内容都使用 Azure 门户中指定的回调 uri

我浏览了多个 MSDN 文档,从 GitHub 下载示例项目,修改了 Android 清单文件等。None 似乎解决了这个问题。我对此束手无策。这是验证码的示例:

        public static IPublicClientApplication PCA;

        //OAuthSettings is a class containing my values to pass to the methods of the 
        //PublicClientApplicationBuilder
        var builder = PublicClientApplicationBuilder
                            .Create(OAuthSettings.ApplicationId)
                            .WithTenantId(OAuthSettings.TenantId)
                            .WithBroker()
                            .WithRedirectUri(OAuthSettings.RedirectUri);

        PCA = builder.Build();

        try
        {
            var accounts = await PCA.GetAccountsAsync();

            var silentAuthResult = await PCA
                .AcquireTokenSilent(new string[] { "api://xxxxxxxxxxxxxx/.default" }, accounts.FirstOrDefault())
                .ExecuteAsync();

            AccessToken = new JwtSecurityToken(silentAuthResult.AccessToken);

            //more code removed for brevity
        }
        catch (MsalUiRequiredException msalEx)
        {
            
            var windowLocatorService = DependencyService.Get<IParentWindowLocatorService>();

            // Prompt the user to sign-in
            var interactiveRequest = PCA.AcquireTokenInteractive(new string[] { "api://xxxxxxxxxxxxxxxxxxx/.default" });

            //Used for Android and iOS
            AuthUIParent = windowLocatorService?.GetCurrentParentWindow();

            if (AuthUIParent != null)
            {
                interactiveRequest = interactiveRequest
                    .WithParentActivityOrWindow(AuthUIParent);
            }
            //

            var interactiveAuthResult = await interactiveRequest.ExecuteAsync();

            AccessToken = new JwtSecurityToken(interactiveAuthResult.AccessToken);
       }

Android 清单

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" package="com.gpdgroup.GPDMobileAppTest" android:installLocation="auto" android:versionCode="7">
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <application android:label="mycompany.Android" android:theme="@style/MainTheme" android:usesCleartextTraffic="true" android:icon="@mipmap/icon" android:roundIcon="@mipmap/icon">
        <activity android:name="microsoft.identity.client.BrowserTabActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="msal{clientID}" android:host="auth" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />               
                <data android:scheme="msauth" android:host="com.mycompany.myapp" android:path="/{base64 hash}" />
            </intent-filter>
        </activity>
    </application>
    <!--Necessary to fix issue on authentication for level 30-->
    <queries>
        <package android:name="com.azure.authenticator" />
        <package android:name="com.mycompany.myapp" />
        <package android:name="com.microsoft.windowsintune.companyportal" />
        <!-- Required for API Level 30 to make sure the app detect browsers
        (that don't support custom tabs) -->
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" />
        </intent>
        <!-- Required for API Level 30 to make sure the app can detect browsers that support custom tabs -->
        <!-- https://developers.google.com/web/updates/2020/07/custom-tabs-android-11#detecting_browsers_that_support_custom_tabs -->
        <intent>
            <action android:name="android.support.customtabs.action.CustomTabsService" />
        </intent>
    </queries>
</manifest>
  

Azure 门户截图:

我还添加了一个 MSAL 身份验证 JSON 文件到 Android 项目的 Resources 文件夹中名为 raw 的子文件夹 msal_default_config.json:

{ 
   "client_id": "xxxxxxxxxxxxxxxxxxxxx", 
   "redirect_uri": "msauth://com.mycompany.myapp/{base64 url encoded signature hash}", 
   "broker_redirect_uri_registered": true, 
   "account_mode" : "SINGLE", 
   "authorities": [ 
   { "type": "AAD", "audience": { "type": "AzureADandPersonalMicrosoftAccount", 
     "tenant_id": "xxxxxxxxxxxxxxxxxx" } 
   } ] 
}

而且我还有这个 class 用于继承 BrowserTabActivity class 的 Android 项目,称为 MsalActivity:

[Activity]
[IntentFilter(new[] { Intent.ActionView },
   Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
   DataHost = "auth",
   DataScheme = "msal{clientID}")]

public class MsalActivity : BrowserTabActivity
{
}

Google 构建一个应用程序可以调用另一个应用程序的机制的方式要求重定向 URI 必须包含应用程序 pkg 的签名。

问题是每个 Android SDK 部署都有自己的签名。所以当你开发应用程序时,你有一个签名。当另一个开发人员尝试构建和部署时,这是另一个签名。当您创建您提交的官方应用程序时,还有另一个签名。他们都使用不同的 Android SDK。

因此您需要为每个应用开发者注册 1 个重定向 URI,并为打包位注册 1 个重定向 URI。

您应该预料到您的应用程序的未来版本将具有相同的签名,因此此过程只执行一次。

所以在多次更改并尝试不同的事情之后,我们让它工作了,但我们必须执行以下操作:

  1. 我让管理 Play 商店的同事创建了一个新的应用程序以供发布。 (不确定是否有必要)

  2. 我将 Android 应用程序的包名称更改为全部小写。 (不确定是否有必要)

  3. 使用以下方法获取生产签名密钥哈希,以便我们可以将其添加到 Azure 门户。

我们如何获得生产签名哈希:

一个。我管理 Play 商店的同事给了我应用程序签名的十六进制 SHA-1 值,来自 Google Play 新创建的应用程序

乙。使用 Sujeet Kumar 的答案,通过将十六进制值输入 Chrome 的控制台 window 来获取 base64 哈希值:

btoa('{your hexadecimal value goes here without the curly brackets}'.split(':')
.map(hc => String.fromCharCode(parseInt(hc, 16))).join(''))

C。获取此 base64 哈希并将其放在 Azure 门户

的应用程序 Android 回调 uri 部分下

由于没有人告诉我如何获取生产签名哈希的最后一步,我认为将我用来获取它的步骤放在这里可能会有所帮助。