Anvisningar: Skriva en TokenProvider med en Azure-funktion

Kommentar

Den här förhandsversionen tillhandahålls utan ett serviceavtal och rekommenderas inte för produktionsarbetsbelastningar. Vissa funktioner kanske inte stöds eller kan vara begränsade.

I Dinamična platforma ansvarar TokenProviders för att skapa och signera token som @fluidframework/azure-client används för att göra begäranden till Azure Fluid Relay-tjänsten. Dinamična platforma tillhandahåller en enkel, osäker TokenProvider för utvecklingsändamål, med namnet InsecureTokenProvider. Varje fluidtjänst måste implementera en anpassad TokenProvider baserat på den specifika tjänstens autentiserings- och säkerhetsöverväganden.

Varje Azure Fluid Relay-resurs som du skapar tilldelas ett klient-ID och en egen unik klienthemlighetsnyckel. Den hemliga nyckeln är en delad hemlighet. Din app/tjänst känner till det och Tjänsten Azure Fluid Relay vet det. TokenProviders måste känna till den hemliga nyckeln för att signera begäranden, men den hemliga nyckeln kan inte ingå i klientkoden.

Implementera en Azure-funktion för att signera token

Ett alternativ för att skapa en säker tokenprovider är att skapa HTTPS-slutpunkt och skapa en TokenProvider-implementering som gör autentiserade HTTPS-begäranden till slutpunkten för att hämta token. Med den här sökvägen kan du lagra klienthemlighetsnyckeln på en säker plats, till exempel Azure Key Vault.

Den fullständiga lösningen har två delar:

  1. En HTTPS-slutpunkt som accepterar begäranden och returnerar Azure Fluid Relay-token.
  2. En ITokenProvider-implementering som accepterar en URL till en slutpunkt och sedan skickar begäranden till slutpunkten för att hämta token.

Skapa en slutpunkt för din TokenProvider med hjälp av Azure Functions

Att använda Azure Functions är ett snabbt sätt att skapa en sådan HTTPS-slutpunkt.

Det här exemplet visar hur du skapar en egen HTTPTrigger Azure-funktion som hämtar token genom att skicka in klientnyckeln.

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;

Funktionen generateToken , som finns i @fluidframework/azure-service-utils paketet, genererar en token för den angivna användaren som är signerad med klientorganisationens hemliga nyckel. Med den här metoden kan token returneras till klienten utan att hemligheten exponeras. I stället genereras token på serversidan med hjälp av hemligheten för att ge begränsad åtkomst till det angivna dokumentet. Exemplet ITokenProvider nedan gör HTTP-begäranden till den här Azure-funktionen för att hämta token.

Distribuera Azure-funktionen

Azure Functions kan distribueras på flera sätt. Mer information finns i avsnittet Distribuera i Azure Functions-dokumentationen för mer information om hur du distribuerar Azure Functions.

Implementera TokenProvider

TokenProviders kan implementeras på många sätt, men måste implementera två separata API-anrop: fetchOrdererToken och fetchStorageToken. Dessa API:er ansvarar för att hämta token för fluidbeställnings- respektive lagringstjänster. Båda funktionerna returnerar TokenResponse objekt som representerar tokenvärdet. Dinamična platforma-körningen anropar dessa två API:er efter behov för att hämta token. Observera att även om programkoden bara använder en tjänstslutpunkt för att upprätta anslutning med Azure Fluid Relay-tjänsten, översätter azure-klienten internt tillsammans med tjänsten den ena slutpunkten till ett beställnings- och lagringsslutpunktspar. Dessa två slutpunkter används från och med då för den sessionen, vilket är anledningen till att du måste implementera de två separata funktionerna för att hämta token, en för var och en.

För att säkerställa att klienthemlighetsnyckeln hålls säker lagras den på en säker serverdelsplats och är endast tillgänglig från Azure-funktionen. Om du vill hämta token måste du göra en GET eller begäran till din distribuerade Azure-funktion och ange tenantID och documentId, ochuserNameuserID/ .POST Azure-funktionen ansvarar för mappningen mellan klient-ID:t och en klientnyckelhemlighet för att generera och signera token på lämpligt sätt.

Exempelimplementeringen nedan hanterar att göra dessa begäranden till din Azure-funktion. Det använder axios-biblioteket för att göra HTTP-begäranden. Du kan använda andra bibliotek eller metoder för att göra en HTTP-begäran från serverkoden. Den här specifika implementeringen tillhandahålls också för dig som en export från @fluidframework/azure-client paketet.

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

Lägg till effektivitet och felhantering

AzureFunctionTokenProvider Är en enkel implementering som TokenProvider ska behandlas som en startpunkt när du implementerar din egen anpassade tokenprovider. För implementering av en produktionsklar tokenprovider bör du överväga olika felscenarier som tokenprovidern behöver hantera. Implementeringen AzureFunctionTokenProvider kan till exempel inte hantera nätverksfrånkopplingssituationer eftersom den inte cachelagr token på klientsidan.

När containern kopplas från försöker anslutningshanteraren hämta en ny token från TokenProvider innan den återansluter till containern. När nätverket är frånkopplat misslyckas API:ets begäran som görs i fetchOrdererToken och utlöser ett fel som inte går att försöka igen. Detta leder i sin tur till att containern tas bort och inte kan återansluta även om en nätverksanslutning återupprättas.

En möjlig lösning för det här frånkopplingsproblemet är att cachelagras giltiga token i Window.localStorage. Med tokencachelagring hämtar containern en giltig lagrad token i stället för att göra en API get-begäran medan nätverket är frånkopplat. Observera att en lokalt lagrad token kan upphöra att gälla efter en viss tidsperiod och att du fortfarande behöver göra en API-begäran för att få en ny giltig token. I det här fallet krävs ytterligare felhantering och omförsökslogik för att förhindra att containern exponeras efter ett enda misslyckat försök.

Hur du väljer att implementera dessa förbättringar är helt upp till dig och kraven för ditt program. Observera att med tokenlösningen localStorage ser du även prestandaförbättringar i ditt program eftersom du tar bort en nätverksbegäran för varje getContainer anrop.

Tokencachelagring med något liknande localStorage kan medföra säkerhetskonsekvenser, och det är upp till dig när du bestämmer vilken lösning som är lämplig för ditt program. Oavsett om du implementerar tokencachelagring eller inte bör du lägga till felhanterings- och återförsökslogik i fetchOrdererToken och fetchStorageToken så att containern inte tas bort efter ett enda misslyckat anrop. Överväg till exempel att omsluta anropet getToken i ett try block med ett catch block som försöker igen och utlöser ett fel först efter ett angivet antal återförsök.

Se även