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

Posted

技术标签:

【中文标题】Xamarin Forms Android - 使用 MSAL 库部署时身份验证失败【英文标题】:Xamarin Forms Android - Authentication Failure on Deployment using MSAL Library 【发布时间】:2021-11-19 22:04:00 【问题描述】:

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

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

Authentication Error [Android broker] broker redirect URI 不正确,应该是 msauth://com.xxxxxx.xxxxxxx/xxxxxxxxxxxxxx 详情请访问https://aka.ms/Brokered-Authentication-for-Android

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

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

        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="msalclientID" 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 门户的屏幕截图:

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

 
   "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"  
    ] 

我也有这个类,用于继承 BrowserTabActivity 类的 Android 项目,称为 MsalActivity:

[Activity]
[IntentFilter(new[]  Intent.ActionView ,
   Categories = new[]  Intent.CategoryBrowsable, Intent.CategoryDefault ,
   DataHost = "auth",
   DataScheme = "msalclientID")]

public class MsalActivity : BrowserTabActivity


【问题讨论】:

【参考方案1】:

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

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

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

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

【讨论】:

我对此表示赞同,但展示如何获取生产签名哈希会很有帮助。我添加了我自己接受的答案,因为它显示了将生产签名哈希添加到 Azure 门户的方法。感谢您的帮助。【参考方案2】:

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

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

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

    使用以下内容获取生产签名密钥哈希,以便我们可以将其添加到 Azure 门户。 Production Signing Hash

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

A.我管理 Play 商店的同事为我提供了来自 Google Play 的应用程序签名的十六进制 SHA-1 值,用于新创建的应用程序

B.使用 Sujeet Kumar 的答案通过将十六进制值输入 Chrome 的控制台窗口来获取 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 部分下

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

【讨论】:

以上是关于Xamarin Forms Android - 使用 MSAL 库部署时身份验证失败的主要内容,如果未能解决你的问题,请参考以下文章

Xamarin.Forms 如何使按钮出现在一行中

Xamarin.forms(或)xamarin.ios/xamarin.android(或)本机

Xamarin.Forms 第23局:手势识别

Xamarin.Forms 是 Xamarin.Android、Xamarin.IoS 和 Xamarin.Win 的简单总和吗?

在 Xamarin.Forms 中使用 Android 绑定

Xamarin.Forms 3.1.0+版本 Android 原生支持底部导航栏