كيفية القيام بما يلي: كتابة TokenProvider مع دالة Azure

إشعار

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

في إطار عمل Fluid، يكون TokenProviders مسؤولا عن إنشاء وتوقيع الرموز المميزة التي @fluidframework/azure-client يستخدمها لتقديم طلبات إلى خدمة Azure Fluid Relay. يوفر إطار عمل Fluid TokenProvider بسيطا وغير آمن لأغراض التطوير، يسمى على نحو مناسب InsecureTokenProvider. يجب على كل خدمة Fluid تنفيذ TokenProvider مخصص استنادا إلى مصادقة الخدمة المعينة واعتبارات الأمان.

يتم تعيين معرف مستأجر لكل مورد Azure Fluid Relay تقوم بإنشائه ومفتاح سر المستأجر الفريد الخاص به. المفتاح السري هو سر مشترك. يعرف التطبيق/الخدمة الخاصة بك ذلك، وتعرفه خدمة Azure Fluid Relay. يجب أن يعرف TokenProviders المفتاح السري لتوقيع الطلبات، ولكن لا يمكن تضمين المفتاح السري في التعليمات البرمجية للعميل.

تنفيذ Azure Function لتوقيع الرموز المميزة

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

يحتوي الحل الكامل على قطعتين:

  1. نقطة نهاية HTTPS تقبل الطلبات وتعيد رموز Azure Fluid Relay المميزة.
  2. تطبيق ITokenProvider الذي يقبل عنوان URL إلى نقطة نهاية، ثم يقدم طلبات إلى نقطة النهاية هذه لاسترداد الرموز المميزة.

إنشاء نقطة نهاية ل TokenProvider باستخدام Azure Functions

يعد استخدام Azure Functions طريقة سريعة لإنشاء نقطة نهاية HTTPS هذه.

يوضح هذا المثال كيفية إنشاء HTTPTrigger Azure Function الخاصة بك التي تجلب الرمز المميز عن طريق تمرير مفتاح المستأجر الخاص بك.

import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { ScopeType } from "@fluidframework/azure-client";
import { generateToken } from "@fluidframework/azure-service-utils";

// NOTE: retrieve the key from a secure location.
const key = "myTenantKey";

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    // tenantId, documentId, userId and userName are required parameters
    const tenantId = (req.query.tenantId || (req.body && req.body.tenantId)) as string;
    const documentId = (req.query.documentId || (req.body && req.body.documentId)) as string | undefined;
    const userId = (req.query.userId || (req.body && req.body.userId)) as string;
    const userName = (req.query.userName || (req.body && req.body.userName)) as string;
    const scopes = (req.query.scopes || (req.body && req.body.scopes)) as ScopeType[];

    if (!tenantId) {
        context.res = {
            status: 400,
            body: "No tenantId provided in query params",
        };
        return;
    }

    if (!key) {
        context.res = {
            status: 404,
            body: `No key found for the provided tenantId: ${tenantId}`,
        };
        return;
    }

    let user = { name: userName, id: userId };

    // Will generate the token and returned by an ITokenProvider implementation to use with the AzureClient.
    const token = generateToken(
        tenantId,
        documentId,
        key,
        scopes ?? [ScopeType.DocRead, ScopeType.DocWrite, ScopeType.SummaryWrite],
        user
    );

    context.res = {
        status: 200,
        body: token
    };
};

export default httpTrigger;

generateToken الدالة، الموجودة في الحزمة@fluidframework/azure-service-utils، تنشئ رمزا مميزا للمستخدم المحدد الذي تم توقيعه باستخدام المفتاح السري للمستأجر. يتيح هذا الأسلوب إرجاع الرمز المميز إلى العميل دون الكشف عن السر. بدلا من ذلك، يتم إنشاء الرمز المميز من جانب الخادم باستخدام السر لتوفير وصول محدد النطاق إلى المستند المحدد. يقوم مثال ITokenProvider أدناه بإجراء طلبات HTTP إلى وظيفة Azure هذه لاسترداد الرموز المميزة.

نشر Azure Function

يمكن نشر Azure Functions بعدة طرق. لمزيد من المعلومات، راجع قسم Deploy في وثائق Azure Functions للحصول على مزيد من المعلومات حول نشر Azure Functions.

تنفيذ TokenProvider

يمكن تنفيذ TokenProviders بعدة طرق، ولكن يجب تنفيذ استدعاءين منفصلين لواجهة برمجة التطبيقات: fetchOrdererToken و fetchStorageToken. واجهات برمجة التطبيقات هذه مسؤولة عن جلب الرموز المميزة لمرسل Fluid وخدمات التخزين على التوالي. ترجع TokenResponse كلتا الدالتين كائنات تمثل قيمة الرمز المميز. يستدعي وقت تشغيل إطار عمل Fluid واجهات برمجة التطبيقات هذين حسب الحاجة لاسترداد الرموز المميزة. لاحظ أنه في حين أن التعليمات البرمجية للتطبيق الخاص بك تستخدم نقطة نهاية خدمة واحدة فقط لإنشاء اتصال بخدمة Azure Fluid Relay، فإن azure-client داخليا بالاقتران مع الخدمة يترجم نقطة النهاية هذه إلى زوج من نقاط نهاية الطلب والتخزين. يتم استخدام نقطتي النهاية هاتين من تلك النقطة في تلك الجلسة ولهذا السبب تحتاج إلى تنفيذ الدالتين المنفصلتين لجلب الرموز المميزة، واحدة لكل منهما.

للتأكد من الاحتفاظ بمفتاح سر المستأجر آمنا، يتم تخزينه في موقع خلفية آمن ويمكن الوصول إليه فقط من داخل Azure Function. لاسترداد الرموز المميزة، تحتاج إلى إجراء GET أو طلب إلى Azure Function المنشورة، وتوفير tenantID و documentIdوuserID/userName.POST تعد Azure Function مسؤولة عن التعيين بين معرف المستأجر وسر مفتاح المستأجر لإنشاء الرمز المميز وتوقيعه بشكل مناسب.

يعالج مثال التنفيذ أدناه إجراء هذه الطلبات إلى وظيفة Azure. ويستخدم مكتبة axios لتقديم طلبات HTTP. يمكنك استخدام مكتبات أو أساليب أخرى لإجراء طلب HTTP من التعليمات البرمجية للخادم. يتم توفير هذا التنفيذ المحدد لك أيضا كمصدر من الحزمة @fluidframework/azure-client .

import { ITokenProvider, ITokenResponse } from "@fluidframework/routerlicious-driver";
import axios from "axios";
import { AzureMember } from "./interfaces";

/**
 * Token Provider implementation for connecting to an Azure Function endpoint for
 * Azure Fluid Relay token resolution.
 */
export class AzureFunctionTokenProvider implements ITokenProvider {
    /**
     * Creates a new instance using configuration parameters.
     * @param azFunctionUrl - URL to Azure Function endpoint
     * @param user - User object
     */
    constructor(
        private readonly azFunctionUrl: string,
        private readonly user?: Pick<AzureMember, "userId" | "userName" | "additionalDetails">,
    ) { }

    public async fetchOrdererToken(tenantId: string, documentId?: string): Promise<ITokenResponse> {
        return {
            jwt: await this.getToken(tenantId, documentId),
        };
    }

    public async fetchStorageToken(tenantId: string, documentId: string): Promise<ITokenResponse> {
        return {
            jwt: await this.getToken(tenantId, documentId),
        };
    }

    private async getToken(tenantId: string, documentId: string | undefined): Promise<string> {
        const response = await axios.get(this.azFunctionUrl, {
            params: {
                tenantId,
                documentId,
                userId: this.user?.userId,
                userName: this.user?.userName,
                additionalDetails: this.user?.additionalDetails,
            },
        });
        return response.data as string;
    }
}

إضافة الكفاءة ومعالجة الأخطاء

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

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

الحل المحتمل لقضية قطع الاتصال هذه هو تخزين الرموز المميزة الصالحة مؤقتا في Window.localStorage. مع التخزين المؤقت للرمز المميز، ستسترد الحاوية رمزا مميزا مخزنا صالحا بدلا من تقديم طلب الحصول على واجهة برمجة التطبيقات أثناء قطع اتصال الشبكة. لاحظ أن الرمز المميز المخزن محليا قد تنتهي صلاحيته بعد فترة زمنية معينة، ولا تزال بحاجة إلى تقديم طلب واجهة برمجة التطبيقات للحصول على رمز مميز صالح جديد. في هذه الحالة، يلزم معالجة الأخطاء الإضافية ومنطق إعادة المحاولة لمنع الحاوية من التخلص بعد محاولة واحدة فاشلة.

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

قد يأتي التخزين المؤقت للرمز المميز بشيء مثل localStorage مع الآثار الأمنية، وهذا أمر متروك لتقديرك عند تحديد الحل المناسب لتطبيقك. سواء قمت بتنفيذ التخزين المؤقت للرمز المميز أم لا، يجب إضافة منطق معالجة الأخطاء وإعادة المحاولة في fetchOrdererToken و fetchStorageToken بحيث لا يتم التخلص من الحاوية بعد استدعاء واحد فاشل. ضع في اعتبارك، على سبيل المثال، التفاف استدعاء getToken في كتلة try مع كتلة catch تعيد المحاولة وتطرح خطأ فقط بعد عدد محدد من عمليات إعادة المحاولة.

(راجع أيضًا )