البرنامج التعليمي: تأمين خدمة Azure Remote Rendering والتخزين النموذجي

في هذا البرنامج التعليمي، تتعلم كيفية:

  • تأمين وحدة Azure Blob Storage التي تحتوي على نماذج لخدمة Azure Remote Rendering
  • المصادقة باستخدام معرف Microsoft Entra للوصول إلى مثيل Azure Remote Rendering
  • استخدام بيانات اعتماد Azure لمصادقة خدمةAzure Remote Rendering

المتطلبات الأساسية

لماذا هناك حاجة إلى مزيد من الأمان

يبدو الوضع الحالي للتطبيق ووصوله إلى مواردك على Azure كما يلي:

Initial security

يحتفظ كل من "AccountID + AccountKey" و"عنوان URL + الرمز المميز لـ SAS" باسم المستخدم وكلمة المرور معًا بشكل أساسي. على سبيل المثال، إذا تم الكشف عن "AccountID + AccountKey"، فقد يكون استخدام المهاجم لموارد ARR الخاصة بك دون إذنك على نفقتك أمرًا بسيطًا.

تأمين المحتوى الخاص بك في وحدة Azure Blob Storage

يمكن لخدمة Azure Remote Rendering الوصول بأمان إلى محتويات وحدة Azure Blob Storage الخاصة بك من خلال التهيئة بشكل صحيح. راجع كيفية: ربط حسابات التخزين لتكوين مثيل Azure Remote Rendering باستخدام حسابات تخزين blob.

عند استخدام تخزين كائن ثنائي كبير الحجم مرتبط، يمكنك استخدام أساليب مختلفة قليلا لتحميل النماذج:

var loadModelParams = new LoadModelFromSasOptions(modelPath, modelEntity);
var task = ARRSessionService.CurrentActiveSession.Connection.LoadModelFromSasAsync(loadModelParams);

تستخدم FromSas الأسطر أعلاه إصدار المعلمات وإجراء الجلسة. يجب تحويلها إلى إصدارات غير SAS:

var loadModelParams = LoadModelOptions.CreateForBlobStorage(storageAccountPath, blobName, modelPath, modelEntity);
var task = ARRSessionService.CurrentActiveSession.Connection.LoadModelAsync(loadModelParams);

دعونا نعدل RemoteRenderingCoordinator لتحميل نموذج مخصص، من حساب تخزين كائن ثنائي كبير الحجم مرتبط.

  1. إذا لم تكن قد قمت بذلك بالفعل، فأكمل كيفية: ربط حسابات التخزين لمنح إذن مثيل ARR للوصول إلى مثيل Blob Storage.

  2. أضف أسلوب LoadModel المعدل التالي إلى RemoteRenderingCoordinator أسفل أسلوب LoadModel الحالي:

    /// <summary>
    /// Loads a model from blob storage that has been linked to the ARR instance
    /// </summary>
    /// <param name="storageAccountName">The storage account name, this contains the blob containers </param>
    /// <param name="blobName">The blob container name, i.e. arroutput</param>
    /// <param name="modelPath">The relative path inside the container to the model, i.e. test/MyCustomModel.arrAsset</param>
    /// <param name="parent">The parent Transform for this remote entity</param>
    /// <param name="progress">A call back method that accepts a float progress value [0->1]</param>
    /// <returns></returns>
    public async Task<Entity> LoadModel(string storageAccountName, string blobName, string modelPath, UnityEngine.Transform parent = null, Action<float> progress = null)
    {
        //Create a root object to parent a loaded model to
        var modelEntity = ARRSessionService.CurrentActiveSession.Connection.CreateEntity();
    
        //Get the game object representation of this entity
        var modelGameObject = modelEntity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
    
        //Ensure the entity will sync its transform with the server
        var sync = modelGameObject.GetComponent<RemoteEntitySyncObject>();
        sync.SyncEveryFrame = true;
    
        //Parent the new object under the defined parent
        if (parent != null)
        {
            modelGameObject.transform.SetParent(parent, false);
            modelGameObject.name = parent.name + "_Entity";
        }
    
        //Load a model that will be parented to the entity
        var loadModelParams = LoadModelOptions.CreateForBlobStorage($"{storageAccountName}.blob.core.windows.net", blobName, modelPath, modelEntity);
        var loadModelAsync = ARRSessionService.CurrentActiveSession.Connection.LoadModelAsync(loadModelParams, progress);
        var result = await loadModelAsync;
        return modelEntity;
    }
    

    هذه التعليمة البرمجية مطابقة للأسلوب الأصلي LoadModel ، ومع ذلك قمنا باستبدال إصدار SAS لاستدعاءات الأسلوب بإصدارات غير SAS.

    المدخلات storageAccountName الإضافية وتمت blobName إضافتها أيضا إلى الوسيطات. نطلق على طريقة LoadModel الجديدة هذه من طريقة أخرى مشابهة لأسلوب LoadTestModel الأول الذي أنشأناه في البرنامج التعليمي الأول.

  3. أضف الأسلوب التالي إلى RemoteRenderingCoordinator بعد LoadTestModel

    private bool loadingLinkedCustomModel = false;
    
    [SerializeField]
    private string storageAccountName;
    public string StorageAccountName {
        get => storageAccountName.Trim();
        set => storageAccountName = value;
    }
    
    [SerializeField]
    private string blobContainerName;
    public string BlobContainerName {
        get => blobContainerName.Trim();
        set => blobContainerName = value;
    }
    
    [SerializeField]
    private string modelPath;
    public string ModelPath {
        get => modelPath.Trim();
        set => modelPath = value;
    }
    
    [ContextMenu("Load Linked Custom Model")]
    public async void LoadLinkedCustomModel()
    {
        if (CurrentCoordinatorState != RemoteRenderingState.RuntimeConnected)
        {
            Debug.LogError("Please wait for the runtime to connect before loading the test model. Try again later.");
            return;
        }
        if (loadingLinkedCustomModel)
        {
            Debug.Log("Linked Test model already loading or loaded!");
            return;
        }
        loadingLinkedCustomModel = true;
    
        // Create a parent object to use for positioning
        GameObject testParent = new GameObject("LinkedCustomModel");
        testParent.transform.position = new Vector3(0f, 0f, 3f);
    
        await LoadModel(StorageAccountName, BlobContainerName, ModelPath, testParent.transform, (progressValue) => Debug.Log($"Loading Test Model progress: {Math.Round(progressValue * 100, 2)}%"));
    }
    

    تضيف هذه التعليمة البرمجية ثلاثة متغيرات سلسلة إضافية إلى مكون RemoteRenderingCoordinator . Screenshot that highlights the Storage Account Name, Blob Container Name, and Model Path of the RemoteRenderingCoordinator component.

  4. أضف قيمك إلى مكون RemoteRenderingCoordinator . بعد اتباع التشغيل السريع لتحويل النموذج، يجب أن تكون قيمك:

    • اسم حساب التخزين: اسم حساب التخزين الخاص بك، الاسم الفريد عالميا الذي تختاره لحساب التخزين الخاص بك. في التشغيل السريع كان هذا arrtutorialstorage، تكون القيمة الخاصة بك مختلفة.
    • Blob Container Name: arroutput، حاوية تخزين Blob
    • مسار النموذج: تركيبة "outputFolderPath" و"outputAssetFileName" المعرفة في ملف arrconfig.json . في التشغيل السريع، كان هذا "outputFolderPath":"converted/robot"، "outputAssetFileName": "robot.arrAsset". مما قد يؤدي إلى قيمة مسار نموذج "converted/robot/robot.arrAsset"، فإن القيمة الخاصة بك مختلفة.

    تلميح

    إذا قمت بتشغيل البرنامج النصي Conversion.ps1 ، دون الوسيطة "-UseContainerSas"، فسيخرج البرنامج النصي جميع القيم أعلاه بدلا من رمز SAS المميز. Linked Model

  5. في الوقت الحالي، قم بإزالة GameObject TestModel أو تعطيله، لتوفير مساحة للنموذج المخصص لتحميله.

  6. تشغيل المشهد والاتصال بجلسة عمل عن بُعد.

  7. افتح قائمة السياق على RemoteRenderingCoordinator وحدد Load Linked Custom Model. Load linked model

لقد ساهمت هذه الخطوات في تعزيز أمان التطبيق عن طريق إزالة الرمز المميز SAS من التطبيق المحلي.

الآن، يبدو الوضع الحالي للتطبيق ووصوله إلى مواردك على Azure كما يلي:

Better security

لدينا "كلمة مرور" أخرى، ألا وهي AccountKey، لإزالتها من التطبيق المحلي. يمكن القيام بذلك باستخدام مصادقة Microsoft Entra.

مصادقة Microsoft Entra

تسمح لك مصادقة Microsoft Entra بتحديد الأفراد أو المجموعات التي تستخدم ARR بطريقة أكثر تحكما. تم إنشاء ARR في دعم لقبول رموز الوصول المميزة بدلا من استخدام مفتاح الحساب. يمكنك افتراض رموز الوصول المميزة كمفتاح محدد زمنيًا، ومخصص للمستخدم، الذي يقوم بإلغاء تأمين أجزاء معينة فقط من المورد المحدد الذي تم طلبه.

يحتوي البرنامج النصي RemoteRenderingCoordinator على مفوض يسمى ARRCredentialGetter، والذي يحتوي على أسلوب يقوم بإرجاع كائن SessionConfiguration، والذي يستخدم لتكوين إدارة جلسة العمل البعيدة. يمكننا تعيين أسلوب مختلف إلى ARRCredentialGetter، ما يسمح لنا باستخدام تدفق تسجيل الدخول إلى Azure، وإنشاء كائن SessionConfiguration الذي يحتوي على رمز مميز للوصول إلى Azure. سيكون رمز الوصول المميز هذا مقتصرًا على المستخدم الذي يقوم بتسجيل الدخول.

  1. اتبع كيفية: تكوين المصادقة - المصادقة للتطبيقات المنشورة، والتي تتضمن تسجيل تطبيق Microsoft Entra جديد وتكوين الوصول إلى مثيل ARR الخاص بك.

  2. بعد تكوين تطبيق Microsoft Entra الجديد، تحقق من أن تطبيق Microsoft Entra الخاص بك يبدو مثل الصور التالية:

    تطبيق Microsoft Entra -> المصادقةApp authentication

    تطبيق Microsoft Entra -> أذونات واجهة برمجة التطبيقاتApp APIs

  3. بعد تهيئة حساب على Remote Rendering، تحقق من أن التهيئة الخاص بك تبدو كالصورة التالية:

    ARR -> AccessControl (IAM)ARR Role

    إشعار

    دور المالك غير كاف لإدارة جلسات العمل عبر تطبيق العميل. لكل مستخدم تريد منحه القدرة على إدارة جلسات العمل، يجب عليك توفير الدور Remote Rendering Client. لكل مستخدم تريد إدارة جلسات العمل وتحويل النماذج، يجب توفير الدور Remote Rendering مسؤول istrator.

مع وجود جانب Azure من الأشياء في مكانها، نحتاج الآن إلى تعديل كيفية اتصال التعليمات البرمجية الخاصة بك بخدمة ARR. نقوم بذلك عن طريق تنفيذ مثيل BaseARRAuthentication، الذي يقوم بإرجاع كائن SessionConfiguration جديد. في هذه الحالة، يتم تكوين معلومات الحساب باستخدام الرمز المميز للوصول إلى Azure.

  1. إنشاء برنامج نصي جديد يسمى AAD (دليل Azure النشط)Authentication واستبدال التعليمات البرمجية الخاصة به مع ما يلي:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Identity.Client;
    using System;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using UnityEngine;
    
    public class AADAuthentication : BaseARRAuthentication
    {
        [SerializeField]
        private string activeDirectoryApplicationClientID;
        public string ActiveDirectoryApplicationClientID
        {
            get => activeDirectoryApplicationClientID.Trim();
            set => activeDirectoryApplicationClientID = value;
        }
    
        [SerializeField]
        private string azureTenantID;
        public string AzureTenantID
        {
            get => azureTenantID.Trim();
            set => azureTenantID = value;
        }
    
        [SerializeField]
        private string azureRemoteRenderingDomain;
        public string AzureRemoteRenderingDomain
        {
            get => azureRemoteRenderingDomain.Trim();
            set => azureRemoteRenderingDomain = value;
        }
    
        [SerializeField]
        private string azureRemoteRenderingAccountID;
        public string AzureRemoteRenderingAccountID
        {
            get => azureRemoteRenderingAccountID.Trim();
            set => azureRemoteRenderingAccountID = value;
        }
    
        [SerializeField]
        private string azureRemoteRenderingAccountDomain;
        public string AzureRemoteRenderingAccountDomain
        {
            get => azureRemoteRenderingAccountDomain.Trim();
            set => azureRemoteRenderingAccountDomain = value;
        }    
    
        public override event Action<string> AuthenticationInstructions;
    
        string authority => "https://login.microsoftonline.com/" + AzureTenantID;
    
        string redirect_uri = "https://login.microsoftonline.com/common/oauth2/nativeclient";
    
        string[] scopes => new string[] { "https://sts.mixedreality.azure.com//.default" };
    
        public void OnEnable()
        {
            RemoteRenderingCoordinator.ARRCredentialGetter = GetARRCredentials;
            this.gameObject.AddComponent<ExecuteOnUnityThread>();
        }
    
        public async override Task<SessionConfiguration> GetARRCredentials()
        {
            var result = await TryLogin();
            if (result != null)
            {
                Debug.Log("Account signin successful " + result.Account.Username);
    
                var AD_Token = result.AccessToken;
    
                return await Task.FromResult(new SessionConfiguration(AzureRemoteRenderingAccountDomain, AzureRemoteRenderingDomain, AzureRemoteRenderingAccountID, "", AD_Token, ""));
            }
            else
            {
                Debug.LogError("Error logging in");
            }
            return default;
        }
    
        private Task DeviceCodeReturned(DeviceCodeResult deviceCodeDetails)
        {
            //Since everything in this task can happen on a different thread, invoke responses on the main Unity thread
            ExecuteOnUnityThread.Enqueue(() =>
            {
                // Display instructions to the user for how to authenticate in the browser
                Debug.Log(deviceCodeDetails.Message);
                AuthenticationInstructions?.Invoke(deviceCodeDetails.Message);
            });
    
            return Task.FromResult(0);
        }
    
        public override async Task<AuthenticationResult> TryLogin()
        {
            var clientApplication = PublicClientApplicationBuilder.Create(ActiveDirectoryApplicationClientID).WithAuthority(authority).WithRedirectUri(redirect_uri).Build();
            AuthenticationResult result = null;
            try
            {
                var accounts = await clientApplication.GetAccountsAsync();
    
                if (accounts.Any())
                {
                    result = await clientApplication.AcquireTokenSilent(scopes, accounts.First()).ExecuteAsync();
    
                    return result;
                }
                else
                {
                    try
                    {
                        result = await clientApplication.AcquireTokenWithDeviceCode(scopes, DeviceCodeReturned).ExecuteAsync(CancellationToken.None);
                        return result;
                    }
                    catch (MsalUiRequiredException ex)
                    {
                        Debug.LogError("MsalUiRequiredException");
                        Debug.LogException(ex);
                    }
                    catch (MsalServiceException ex)
                    {
                        Debug.LogError("MsalServiceException");
                        Debug.LogException(ex);
                    }
                    catch (MsalClientException ex)
                    {
                        Debug.LogError("MsalClientException");
                        Debug.LogException(ex);
                        // Mitigation: Use interactive authentication
                    }
                    catch (Exception ex)
                    {
                        Debug.LogError("Exception");
                        Debug.LogException(ex);
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.LogError("GetAccountsAsync");
                Debug.LogException(ex);
            }
    
            return null;
        }
    }
    

إشعار

هذا الرمز غير مكتمل بأي حال من الأحوال، وغير جاهز للتطبيق التجاري. على سبيل المثال، على أقل تقدير، من المرجح أيضًا أن تفضل إضافة إمكانية تسجيل الخروج. يمكن القيام بذلك باستخدام الأسلوب الذي Task RemoveAsync(IAccount account) يوفره تطبيق العميل. هذا الرمز مخصص فقط للاستخدام في البرنامج التعليمي، وسيكون قيامك بالتنفيذ مقتصر على التطبيق الخاص بك.

يحاول الرمز أولا الحصول على الرمز المميز بصمت باستخدام AquireTokenSilent. يكون هذا ناجحا إذا قام المستخدم مسبقا بمصادقة هذا التطبيق. إذا فشل الأمر، فانتقل إلى إستراتيجية تزيد من مشاركة المستخدم.

بالنسبة إلى هذه التعليمات البرمجية ، نستخدم تدفق رمز الجهاز للحصول على رمز مميز للوصول. يسمح هذا التدفق للمستخدم بتسجيل الدخول إلى حساب Azure الخاص به على جهاز كمبيوتر أو جهاز محمول، وإرسال الرمز المميز الناتج مرة أخرى إلى تطبيق HoloLens.

الجزء الأكثر أهمية من هذه الفئة من منظور ARR هو هذا السطر:

return await Task.FromResult(new SessionConfiguration(AzureRemoteRenderingAccountDomain, AzureRemoteRenderingDomain, AzureRemoteRenderingAccountID, "", AD_Token, ""));

هنا، نقوم بإنشاء كائن SessionConfiguration جديد باستخدام مجال العرض عن بعد ومعرف الحساب ومجال الحساب ورمز الوصول المميز. ثم تستعين خدمة ARR بهذا الرمز المميز للاستعلام عن دورات العرض البعيد وإنشائها، والانضمام إليها ما دام المستخدم يتمتع بالترخيص استنادًا إلى الأذونات محددة الأدوار التي تمت تهيئتها مسبقًا.

مع هذا التغيير، تبدو الحالة الحالية للتطبيق، ووصوله إلى مواردك على Azure كما يلي:

Even better security

نظرا لأن بيانات اعتماد المستخدم لا يتم تخزينها على الجهاز (أو في هذه الحالة حتى تم إدخالها على الجهاز)، فإن خطر التعرض لها منخفض. يستخدم الجهاز الآن رمز وصول مميز خاص بالمستخدم، ومحدد زمنيًا للوصول إلى ARR الذي يستعين بمراقبة الوصول (إدارة الهوية والوصول) للوصول إلى تخزين Blob. قامت هاتان الخطوتان بإزالة "كلمات المرور" من التعليمات البرمجية المصدر وزيادة الأمان بشكل كبير. ومع ذلك، إزاء أن هذه لا تُعدّ أكثر طريقة أمان متاحة، فإن نقل النموذج وإدارة جلسة العمل لخدمة الويب سيُحسّن الأمان بشكلٍ أكثر. تتم مناقشة اعتبارات الأمان الإضافية في فصل الجاهزية التجارية.

اختبار مصادقة Microsoft Entra

في محرر Unity، عندما يكون Microsoft Entra auth نشطا، تحتاج إلى المصادقة في كل مرة تقوم فيها بتشغيل التطبيق. على الجهاز، تحدث خطوة المصادقة في المرة الأولى ولا تكون مطلوبة إلا مرة أخرى عند انتهاء صلاحية الرمز المميز أو إبطاله.

  1. أضف مكون مصادقة Microsoft Entra إلى RemoteRenderingCoordinator GameObject.

    Microsoft Entra auth component

إشعار

إذا كنت تستخدم المشروع المكتمل من مستودع عينات ARR، فتأكد من تمكين مكون مصادقة Microsoft Entra بالنقر فوق خانة الاختيار بجوار عنوانه.

  1. قم بملء قيمك الخاصة بمعرّف العميل ومعرّف المستأجر. يمكن العثور على هذه القيم في صفحة النظرة العامة على تسجيل التطبيق:

    • معرف عميل تطبيق Active Directory هو معرف التطبيق (العميل) الموجود في تسجيل تطبيق Microsoft Entra (راجع الصورة أدناه).
    • معرف مستأجر Azure هو معرف الدليل (المستأجر) الموجود في تسجيل تطبيق Microsoft Entra (راجع الصورة أدناه).
    • مجال Azure Remote Rendering هو نفس المجال الذي كنت تستخدمه في مجال العرض البعيد ل RemoteRenderingCoordinator.
    • معرف حساب Azure Remote Rendering هو نفس معرف الحساب الذي كنت تستخدمه ل RemoteRenderingCoordinator.
    • مجال حساب Azure Remote Rendering هو نفس مجال الحساب الذي كنت تستخدمه في RemoteRenderingCoordinator.

    Screenshot that highlights the Application (client) ID and Directory (tenant) ID.

  2. اضغط على تشغيل في محرر Unity، ووافق على تشغيل جلسة عمل. نظرا لأن مكون مصادقة Microsoft Entra يحتوي على وحدة تحكم في العرض، يتم توصيله تلقائيا لعرض مطالبة بعد لوحة مشروطة لتخويل جلسة العمل.

  3. اتبع الإرشادات الموجودة في اللوحة على يمين AppMenu. يجب أن ترى شيئا مشابها لهذا: Illustration that shows the instruction panel that appears to the right of the AppMenu.

    بعد إدخال الرمز المتوفر على جهازك الثانوي (أو المتصفح على الجهاز نفسه)، وتسجيل الدخول باستخدام بيانات الاعتماد الخاصة بك، سيتم إرجاع رمز الوصول المميز إلى التطبيق صاحب الطلب، وفي هذه الحالة، هو محرر Unity.

بعد هذه المرحلة، يجب أن يستمر كل شيء في التطبيق في العمل بشكل طبيعي. تحقق من وحدة التحكم بـ Unity بحثًا عن أي أخطاء إذا كنت لا تحرز تقدمًا عبر المراحل كما هو متوقع.

إنشاء على جهاز

إذا كنت تقوم بإنشاء تطبيق باستخدام MSAL على الجهاز، فستحتاج إلى تضمين ملف في مجلد Assets الخاص بمشروعك. يساعد هذا المحول البرمجي على إنشاء التطبيق بشكل صحيح باستخدام Microsoft.Identity.Client.dll المضمن في أصول البرنامج التعليمي.

  1. إضافة ملف جديد في الأصول المسماة link.xml

  2. أضِف ما يلي إلى الملف:

    <linker>
        <assembly fullname="Microsoft.Identity.Client" preserve="all"/>
        <assembly fullname="System.Runtime.Serialization" preserve="all"/>
        <assembly fullname="System.Core">
            <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
        </assembly>
    </linker>
    
  3. ‏‏حفظ التغييرات

اتبع الخطوات الموجودة في التشغيل السريع: نشر نموذج Unity إلى HoloLens - إنشاء نموذج المشروع، للبناء إلى HoloLens.

الخطوات التالية

يحتوي الجزء المتبقي من مجموعة البرامج التعليمية هذه على مقالات مفاهيمية لإنشاء تطبيق جاهز للإنتاج يستخدم Azure Remote Rendering.