Procedure: Een TokenProvider schrijven met een Azure-functie
Notitie
Deze preview-versie wordt aangeboden zonder Service Level Agreement en wordt niet aanbevolen voor productieworkloads. Misschien worden bepaalde functies niet ondersteund of zijn de mogelijkheden ervan beperkt.
In het Fluid Framework zijn TokenProviders verantwoordelijk voor het maken en ondertekenen van tokens die worden @fluidframework/azure-client
gebruikt om aanvragen te verzenden naar de Azure Fluid Relay-service. Het Fluid Framework biedt een eenvoudige, onveilige TokenProvider voor ontwikkelingsdoeleinden, toepasselijk genaamd InsecureTokenProvider. Elke Fluid-service moet een aangepaste TokenProvider implementeren op basis van de verificatie- en beveiligingsoverwegingen van de specifieke service.
Aan elke Azure Fluid Relay-resource die u maakt, wordt een tenant-id en een eigen unieke tenantgeheimsleutel toegewezen. De geheime sleutel is een gedeeld geheim. Uw app/service kent dit en de Azure Fluid Relay-service weet het. TokenProviders moet de geheime sleutel kennen om aanvragen te ondertekenen, maar de geheime sleutel kan niet worden opgenomen in de clientcode.
Een Azure-functie implementeren om tokens te ondertekenen
Een optie voor het bouwen van een beveiligde tokenprovider is het maken van een HTTPS-eindpunt en het maken van een TokenProvider-implementatie waarmee geverifieerde HTTPS-aanvragen naar dat eindpunt worden verzonden om tokens op te halen. Met dit pad kunt u de geheime tenantsleutel opslaan op een veilige locatie, zoals Azure Key Vault.
De complete oplossing bestaat uit twee onderdelen:
- Een HTTPS-eindpunt dat aanvragen accepteert en Azure Fluid Relay-tokens retourneert.
- Een ITokenProvider-implementatie die een URL naar een eindpunt accepteert en vervolgens aanvragen naar dat eindpunt indient om tokens op te halen.
Een eindpunt voor uw TokenProvider maken met behulp van Azure Functions
Het gebruik van Azure Functions is een snelle manier om een dergelijk HTTPS-eindpunt te maken.
In dit voorbeeld ziet u hoe u uw eigen HTTPTrigger Azure-functie maakt die het token ophaalt door uw tenantsleutel door te geven.
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;
De generateToken
functie, gevonden in het @fluidframework/azure-service-utils
pakket, genereert een token voor de opgegeven gebruiker dat is ondertekend met behulp van de geheime sleutel van de tenant. Met deze methode kan het token worden geretourneerd naar de client zonder dat het geheim wordt weergegeven. In plaats daarvan wordt het token aan de serverzijde gegenereerd met behulp van het geheim om toegang tot het opgegeven document binnen een bereik te bieden. In het onderstaande voorbeeld van ITokenProvider worden HTTP-aanvragen naar deze Azure-functie verzonden om de tokens op te halen.
De Azure-functie implementeren
Azure Functions kan op verschillende manieren worden geïmplementeerd. Zie de sectie Implementeren van de documentatie voor Azure Functions voor meer informatie over het implementeren van Azure Functions.
TokenProvider implementeren
TokenProviders kunnen op verschillende manieren worden geïmplementeerd, maar moeten twee afzonderlijke API-aanroepen implementeren: fetchOrdererToken
en fetchStorageToken
. Deze API's zijn verantwoordelijk voor het ophalen van tokens voor respectievelijk de Fluid-orderer en opslagservices. Beide functies retourneren TokenResponse
objecten die de tokenwaarde vertegenwoordigen. De Fluid Framework-runtime roept deze twee API's aan indien nodig om tokens op te halen. Houd er rekening mee dat hoewel uw toepassingscode slechts één service-eindpunt gebruikt om verbinding te maken met de Azure Fluid Relay-service, de azure-client intern in combinatie met de service dat ene eindpunt omzetten in een combinatie van een orderer- en opslageindpunt. Deze twee eindpunten worden vanaf dat moment gebruikt voor die sessie. Daarom moet u de twee afzonderlijke functies implementeren voor het ophalen van tokens, één voor elk.
Om ervoor te zorgen dat de geheime tenantsleutel veilig blijft, wordt deze opgeslagen op een veilige back-endlocatie en is deze alleen toegankelijk vanuit de Azure-functie. Als u tokens wilt ophalen, moet u een GET
of-aanvraag POST
indienen bij uw geïmplementeerde Azure-functie, waarbij u de tenantID
en en documentId
oplevert userID
/userName
. De Azure-functie is verantwoordelijk voor de toewijzing tussen de tenant-id en een tenantsleutelgeheim om het token op de juiste manier te genereren en te ondertekenen.
De onderstaande voorbeeld-implementatie verwerkt het indienen van deze aanvragen bij uw Azure-functie. Het maakt gebruik van de axios-bibliotheek om HTTP-aanvragen te doen. U kunt andere bibliotheken of benaderingen gebruiken om een HTTP-aanvraag te maken vanuit servercode. Deze specifieke implementatie wordt ook voor u aangeboden als een export uit het @fluidframework/azure-client
pakket.
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;
}
}
Efficiëntie en foutafhandeling toevoegen
De AzureFunctionTokenProvider
is een eenvoudige implementatie van die moet worden behandeld als uitgangspunt bij het implementeren van TokenProvider
uw eigen aangepaste tokenprovider. Voor de implementatie van een tokenprovider die gereed is voor productie, moet u rekening houden met verschillende foutscenario's die de tokenprovider moet verwerken. De implementatie kan bijvoorbeeld AzureFunctionTokenProvider
geen situaties met netwerkverbinding afhandelen omdat het token niet in de cache wordt opgeslagen aan de clientzijde.
Wanneer de verbinding met de container wordt verbroken, probeert verbindingsbeheer een nieuw token op te halen uit tokenProvider voordat er opnieuw verbinding wordt gemaakt met de container. Terwijl de verbinding met het netwerk is verbroken, mislukt de api-aanvraag voor ophalen in fetchOrdererToken
en treedt er een fout op die niet opnieuw kan worden geprobeerd. Dit leidt er weer toe dat de container wordt verwijderd en niet opnieuw verbinding kan maken, zelfs niet als een netwerkverbinding opnieuw tot stand wordt gebracht.
Een mogelijke oplossing voor dit probleem met de verbinding is het opslaan van geldige tokens in de cache in Window.localStorage. Met tokencaching haalt de container een geldig opgeslagen token op in plaats van een API get-aanvraag te maken terwijl de verbinding met het netwerk wordt verbroken. Houd er rekening mee dat een lokaal opgeslagen token na een bepaalde periode kan verlopen en dat u nog steeds een API-aanvraag moet indienen om een nieuw geldig token op te halen. In dit geval is extra foutafhandeling en logica voor opnieuw proberen vereist om te voorkomen dat de container na één mislukte poging wordt verwijderd.
Hoe u ervoor kiest om deze verbeteringen te implementeren, is volledig aan u en aan de vereisten van uw toepassing. Houd er rekening mee dat u met de localStorage
tokenoplossing ook prestatieverbeteringen in uw toepassing ziet, omdat u bij elke getContainer
aanroep een netwerkaanvraag verwijdert.
Tokencaching met iets als localStorage
dit kan gevolgen hebben voor de beveiliging en het is aan u om te bepalen welke oplossing geschikt is voor uw toepassing. Ongeacht of u tokencaching implementeert of niet, moet u logica voor foutafhandeling en nieuwe pogingen toevoegen in fetchOrdererToken
en fetchStorageToken
zodat de container niet wordt verwijderd na één mislukte aanroep. Denk bijvoorbeeld aan het verpakken van de aanroep van getToken
in een try
blok met een catch
blok dat pas na een opgegeven aantal nieuwe pogingen een fout genereert.