Практическое руководство. Создание TokenProvider с помощью функции Azure

Примечание.

Эта предварительная версия предоставляется без соглашения об уровне обслуживания. Ее не следует использовать для производственных рабочих нагрузок. Некоторые функции могут не поддерживаться или их возможности могут быть ограничены.

В Fluid Framework TokenProviders отвечают за создание и подписывание маркеров, которые @fluidframework/azure-client используются для выполнения запросов к службе Ретранслятора Жидкости Azure. Fluid Framework предоставляет простой небезопасный токенProvider для целей разработки с именем InsecureTokenProvider. Каждая служба "Жидкость" должна реализовать пользовательский tokenProvider на основе проверки подлинности и безопасности конкретной службы.

Каждый создаваемый ресурс Azure Fluid Relay назначается идентификатор клиента и собственный уникальный секретный ключ клиента. Секретный ключ — это общий секрет. Ваше приложение или служба знает его, и служба Ретранслятора Azure знает его. TokenProviders должен знать секретный ключ для подписывания запросов, но секретный ключ не может быть включен в клиентский код.

Реализация функции Azure для подписывания маркеров

Один из вариантов создания поставщика безопасных маркеров — создать конечную точку HTTPS и создать реализацию TokenProvider, которая делает прошедшие проверку подлинности HTTPS-запросы к этой конечной точке для получения маркеров. Этот путь позволяет хранить секретный ключ клиента в безопасном расположении, например Azure Key Vault.

Полное решение состоит из двух частей:

  1. Конечная точка HTTPS, принимаюющая запросы и возвращающая маркеры Ретранслятора Жидкости Azure.
  2. Реализация ITokenProvider, которая принимает URL-адрес конечной точки, а затем отправляет запросы к этой конечной точке для получения маркеров.

Создание конечной точки для TokenProvider с помощью Функции Azure

Использование Функции Azure — это быстрый способ создания такой конечной точки HTTPS.

В этом примере показано, как создать собственную функцию AZURE HTTPTrigger, которая извлекает маркер, передав ключ клиента.

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

Функции Azure можно развернуть несколькими способами. Дополнительные сведения см. в разделе "Развертывание" документации по Функции Azure для получения дополнительных сведений о развертывании Функции Azure.

Реализация TokenProvider

TokenProviders можно реализовать различными способами, но необходимо реализовать два отдельных вызова API: fetchOrdererToken и fetchStorageToken. Эти API отвечают за получение маркеров для служб упорядочения жидкости и хранилища соответственно. Обе функции возвращают TokenResponse объекты, представляющие значение токена. Среда выполнения Fluid Framework вызывает эти два API, чтобы получить маркеры. Обратите внимание, что хотя код приложения использует только одну конечную точку службы для установления подключения к службе Azure Fluid Relay, клиент Azure внутренне в сочетании со службой преобразует одну конечную точку в пару упорядочения и конечной точки хранилища. Эти две конечные точки используются с этого момента для этого сеанса, поэтому необходимо реализовать две отдельные функции для получения маркеров, по одному для каждого.

Чтобы обеспечить безопасность ключа секрета клиента, он хранится в защищенном серверном расположении и доступен только в функции Azure. Чтобы получить маркеры, необходимо выполнить или запросить развернутую GET функцию Azure, предоставляя tenantID и (и) .userIDdocumentId/userNamePOST Функция Azure отвечает за сопоставление идентификатора клиента и секрета ключа клиента для соответствующего создания и подписи маркера.

В приведенном ниже примере реализация обрабатывает эти запросы в функцию 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, прежде чем повторно подключиться к контейнеру. Хотя сеть отключена, запрос на получение API, выполненный, fetchOrdererToken завершится ошибкой и вызовет ошибку без повторных попыток. Это, в свою очередь, приводит к удалению контейнера и не может повторно подключиться, даже если сетевое подключение повторно установлено.

Потенциальное решение этой проблемы отключения заключается в кэшировании допустимых маркеров в Window.local служба хранилища. При кэшировании маркеров контейнер получит действительный сохраненный маркер вместо того, чтобы получить запрос API во время отключения сети. Обратите внимание, что срок действия локально хранимого токена может истекать через определенный период времени, и вам по-прежнему потребуется запросить API, чтобы получить новый действительный маркер. В этом случае потребуется дополнительная обработка ошибок и логика повторных попыток, чтобы предотвратить удаление контейнера после одной неудачной попытки.

Способ реализации этих улучшений полностью соответствует вашим требованиям и требованиям вашего приложения. Обратите внимание, что с localStorage помощью решения маркера вы также увидите улучшения производительности приложения, так как вы удаляете сетевой запрос по каждому getContainer вызову.

Кэширование маркеров с каким-то таким localStorage образом может привести к последствиям безопасности, и это касается вашего усмотрения при принятии решения о том, какое решение подходит для вашего приложения. Независимо от того, реализуется ли кэширование маркеров, следует добавить логику fetchOrdererToken обработки ошибок и повторных попыток, чтобы fetchStorageToken контейнер не был удален после одного неудачного вызова. Рассмотрим, например, оболочку вызова getToken в try блок с блоком catch , который повторяется и выдает ошибку только после указанного числа повторных попыток.

См. также