Instrukcje: pisanie tokenProvider za pomocą funkcji platformy Azure

Uwaga

Ta wersja zapoznawcza jest udostępniana bez umowy dotyczącej poziomu usług i nie jest zalecana w przypadku obciążeń produkcyjnych. Niektóre funkcje mogą być nieobsługiwane lub ograniczone.

W Elastyczna struktura dostawcy tokenów są odpowiedzialni za tworzenie i podpisywanie tokenów używanych @fluidframework/azure-client do wprowadzania żądań do usługi Azure Fluid Relay. Elastyczna struktura zapewnia prosty, niezabezpieczony tokenProvider do celów programistycznych, trafnie o nazwie InsecureTokenProvider. Każda usługa fluidu musi implementować niestandardowy tokenProvider na podstawie uwierzytelniania i zabezpieczeń konkretnej usługi.

Każdy utworzony zasób usługi Azure Fluid Relay ma przypisany identyfikator dzierżawy i własny unikatowy klucz tajny dzierżawy. Klucz tajny jest wspólnym kluczem tajnym. Twoja aplikacja/usługa go zna, a usługa Azure Fluid Relay ją zna. TokenProviders muszą znać klucz tajny do podpisywania żądań, ale klucz tajny nie może być uwzględniony w kodzie klienta.

Implementowanie funkcji platformy Azure w celu podpisywania tokenów

Jedną z opcji tworzenia dostawcy bezpiecznego tokenu jest utworzenie punktu końcowego HTTPS i utworzenie implementacji TokenProvider, która wysyła uwierzytelnione żądania HTTPS do tego punktu końcowego w celu pobrania tokenów. Ta ścieżka umożliwia przechowywanie klucza tajnego dzierżawy w bezpiecznej lokalizacji, takiej jak usługa Azure Key Vault.

Kompletne rozwiązanie ma dwa elementy:

  1. Punkt końcowy HTTPS, który akceptuje żądania i zwraca tokeny usługi Azure Fluid Relay.
  2. Implementacja ITokenProvider, która akceptuje adres URL punktu końcowego, a następnie wysyła żądania do tego punktu końcowego w celu pobrania tokenów.

Tworzenie punktu końcowego dla dostawcy tokenów przy użyciu usługi Azure Functions

Korzystanie z usługi Azure Functions to szybki sposób tworzenia takiego punktu końcowego HTTPS.

W tym przykładzie pokazano, jak utworzyć własną funkcję platformy Azure HTTPTrigger, która pobiera token, przekazując klucz dzierżawy.

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;

Funkcja generateToken znaleziona w @fluidframework/azure-service-utils pakiecie generuje token dla danego użytkownika, który jest podpisany przy użyciu klucza tajnego dzierżawy. Ta metoda umożliwia zwrócenie tokenu do klienta bez uwidaczniania wpisu tajnego. Zamiast tego token jest generowany po stronie serwera przy użyciu wpisu tajnego w celu zapewnienia dostępu w zakresie do danego dokumentu. Poniższy przykład ITokenProvider wysyła żądania HTTP do tej funkcji platformy Azure w celu pobrania tokenów.

Wdrażanie funkcji platformy Azure

Usługę Azure Functions można wdrożyć na kilka sposobów. Aby uzyskać więcej informacji, zobacz sekcję Wdrażanie dokumentacji usługi Azure Functions, aby uzyskać więcej informacji na temat wdrażania usługi Azure Functions.

Implementowanie elementu TokenProvider

TokenProvider można zaimplementować na wiele sposobów, ale musi implementować dwa oddzielne wywołania interfejsu API: fetchOrdererToken i fetchStorageToken. Te interfejsy API są odpowiedzialne za pobieranie tokenów odpowiednio dla usług porządkowania płynów i magazynowania. Obie funkcje zwracają TokenResponse obiekty reprezentujące wartość tokenu. Środowisko uruchomieniowe Elastyczna struktura wywołuje te dwa interfejsy API w razie potrzeby w celu pobrania tokenów. Należy pamiętać, że chociaż kod aplikacji używa tylko jednego punktu końcowego usługi do nawiązywania łączności z usługą Azure Fluid Relay, klient azure-client wewnętrznie w połączeniu z usługą tłumaczy ten jeden punkt końcowy na parę punktów końcowych zamówienia i punktu końcowego magazynu. Te dwa punkty końcowe są używane od tego momentu dla tej sesji, dlatego należy zaimplementować dwie oddzielne funkcje do pobierania tokenów, po jednym dla każdego.

Aby upewnić się, że klucz tajny dzierżawy jest bezpieczny, jest przechowywany w bezpiecznej lokalizacji zaplecza i jest dostępny tylko z poziomu funkcji platformy Azure. Aby pobrać tokeny, musisz wysłać GET żądanie lub POST do wdrożonej funkcji platformy Azure, podając tenantID wartości i i .userID/userNamedocumentId Funkcja platformy Azure jest odpowiedzialna za mapowanie między identyfikatorem dzierżawy a kluczem tajnym dzierżawy w celu odpowiedniego wygenerowania i podpisania tokenu.

Poniższa przykładowa implementacja obsługuje wykonywanie tych żądań do funkcji platformy Azure. Używa biblioteki axios do wykonywania żądań HTTP. Możesz użyć innych bibliotek lub podejść do tworzenia żądania HTTP z kodu serwera. Ta konkretna implementacja jest również udostępniana jako eksport z @fluidframework/azure-client pakietu.

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;
    }
}

Dodawanie wydajności i obsługi błędów

Jest AzureFunctionTokenProvider to prosta implementacja TokenProvider , której należy traktować jako punkt wyjścia podczas implementowania własnego niestandardowego dostawcy tokenów. W przypadku implementacji dostawcy tokenów gotowego do produkcji należy wziąć pod uwagę różne scenariusze awarii, które dostawca tokenów musi obsługiwać. Na przykład implementacja AzureFunctionTokenProvider nie obsługuje sytuacji rozłączenia sieci, ponieważ nie buforuje tokenu po stronie klienta.

Po rozłączeniu kontenera menedżer połączeń próbuje pobrać nowy token z tokenProvider przed ponownym nawiązaniem połączenia z kontenerem. Gdy sieć jest rozłączona, żądanie pobierania interfejsu API wykonane w fetchOrdererToken programie zakończy się niepowodzeniem i zgłosi błąd niemożliwy do ponowienia próby. Z kolei prowadzi to do usunięcia kontenera i nie jest w stanie ponownie nawiązać połączenia, nawet jeśli połączenie sieciowe zostanie ponownie nawiązane.

Potencjalnym rozwiązaniem tego problemu z rozłączeniem jest buforowanie prawidłowych tokenów w pliku Window.localStorage. Buforowanie tokenów spowoduje pobranie prawidłowego przechowywanego tokenu zamiast żądania pobrania interfejsu API podczas odłączania sieci. Pamiętaj, że token przechowywany lokalnie może wygasnąć po upływie określonego czasu i nadal trzeba będzie wysłać żądanie interfejsu API, aby uzyskać nowy prawidłowy token. W takim przypadku wymagana byłaby dodatkowa obsługa błędów i logika ponawiania prób, aby uniemożliwić kontenerowi usuwanie po jednej nieudanej próbie.

Sposób wdrażania tych ulepszeń jest całkowicie do Ciebie i wymagań aplikacji. Pamiętaj, że w przypadku rozwiązania tokenu localStorage zobaczysz również ulepszenia wydajności w aplikacji, ponieważ usuwasz żądanie sieciowe dla każdego getContainer wywołania.

Buforowanie tokenów z czymś podobnych localStorage może mieć wpływ na bezpieczeństwo i zależy od uznania podczas podejmowania decyzji o tym, jakie rozwiązanie jest odpowiednie dla aplikacji. Niezależnie od tego, czy zaimplementowano buforowanie tokenów, należy dodać logikę obsługi błędów i ponowić próbę w fetchOrdererToken programie i fetchStorageToken tak, aby kontener nie został usunięty po pojedynczym wywołaniu, które zakończyło się niepowodzeniem. Rozważmy na przykład zawijanie wywołania getToken w try bloku za pomocą catch bloku, który ponawia próbę i zgłasza błąd dopiero po określonej liczbie ponownych prób.

Zobacz też