Como: Escrever um TokenProvider com uma função do Azure
Nota
Esta versão de pré-visualização é fornecida sem um contrato de nível de serviço e não é recomendada para cargas de trabalho de produção. Algumas funcionalidades poderão não ser suportadas ou poderão ter capacidades limitadas.
No Fluid Framework, os TokenProviders são responsáveis por criar e assinar tokens que o @fluidframework/azure-client
usa para fazer solicitações ao serviço Azure Fluid Relay. O Fluid Framework fornece um TokenProvider simples e inseguro para fins de desenvolvimento, apropriadamente chamado InsecureTokenProvider. Cada serviço Fluid deve implementar um TokenProvider personalizado com base nas considerações de autenticação e segurança do serviço específico.
Cada recurso do Azure Fluid Relay criado recebe uma ID de locatário e sua própria chave secreta de locatário exclusiva. A chave secreta é um segredo partilhado. Seu aplicativo/serviço sabe disso, e o serviço Azure Fluid Relay sabe disso. TokenProviders deve saber a chave secreta para assinar solicitações, mas a chave secreta não pode ser incluída no código do cliente.
Implementar uma função do Azure para assinar tokens
Uma opção para criar um provedor de token seguro é criar um ponto de extremidade HTTPS e criar uma implementação TokenProvider que faça solicitações HTTPS autenticadas para esse ponto de extremidade para recuperar tokens. Esse caminho permite armazenar a chave secreta do locatário em um local seguro, como o Cofre da Chave do Azure.
A solução completa tem duas partes:
- Um ponto de extremidade HTTPS que aceita solicitações e retorna tokens do Azure Fluid Relay.
- Uma implementação ITokenProvider que aceita uma URL para um ponto de extremidade e, em seguida, faz solicitações a esse ponto de extremidade para recuperar tokens.
Criar um ponto de extremidade para seu TokenProvider usando o Azure Functions
Usar o Azure Functions é uma maneira rápida de criar esse ponto de extremidade HTTPS.
Este exemplo demonstra como criar sua própria Função do Azure HTTPTrigger que busca o token passando sua chave de locatário.
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;
A generateToken
função, encontrada no @fluidframework/azure-service-utils
pacote, gera um token para determinado usuário que é assinado usando a chave secreta do locatário. Esse método permite que o token seja retornado ao cliente sem expor o segredo. Em vez disso, o token é gerado no lado do servidor usando o segredo para fornecer acesso com escopo ao documento determinado. O exemplo ITokenProvider abaixo faz solicitações HTTP para esta Função do Azure para recuperar os tokens.
Implantar a função do Azure
O Azure Functions pode ser implantado de várias maneiras. Para obter mais informações, consulte a seção Implantar da documentação do Azure Functions para obter mais informações sobre como implantar o Azure Functions.
Implementar o TokenProvider
TokenProviders pode ser implementado de várias maneiras, mas deve implementar duas chamadas de API separadas: fetchOrdererToken
e fetchStorageToken
. Essas APIs são responsáveis por buscar tokens para os serviços de encomendador e armazenamento do Fluid, respectivamente. Ambas as funções retornam TokenResponse
objetos que representam o valor do token. O tempo de execução do Fluid Framework chama essas duas APIs conforme necessário para recuperar tokens. Observe que, embora o código do aplicativo esteja usando apenas um ponto de extremidade de serviço para estabelecer conectividade com o serviço Azure Fluid Relay, o azure-client internamente em conjunto com o serviço converte esse ponto de extremidade em um par de ponto de extremidade de pedido e armazenamento. Esses dois pontos de extremidade são usados a partir desse ponto para essa sessão, e é por isso que você precisa implementar as duas funções separadas para buscar tokens, uma para cada um.
Para garantir que a chave secreta do locatário seja mantida segura, ela é armazenada em um local de back-end seguro e só é acessível de dentro da Função do Azure. Para recuperar tokens, você precisa fazer uma GET
ou solicitação para sua Função do Azure implantada, fornecendo o tenantID
e documentId
, euserName
userID
/ .POST
A Função do Azure é responsável pelo mapeamento entre a ID do locatário e um segredo de chave de locatário para gerar e assinar adequadamente o token.
O exemplo de implementação abaixo lida com a realização dessas solicitações para sua Função do Azure. Ele usa a biblioteca axios para fazer solicitações HTTP. Você pode usar outras bibliotecas ou abordagens para fazer uma solicitação HTTP do código do servidor. Essa implementação específica também é fornecida para você como uma exportação do @fluidframework/azure-client
pacote.
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;
}
}
Adicione eficiência e tratamento de erros
A AzureFunctionTokenProvider
é uma implementação simples da qual deve ser tratada como um ponto de partida ao implementar seu próprio provedor de TokenProvider
token personalizado. Para a implementação de um provedor de token pronto para produção, você deve considerar vários cenários de falha que o provedor de token precisa lidar. Por exemplo, a AzureFunctionTokenProvider
implementação falha ao lidar com situações de desconexão de rede porque não armazena em cache o token no lado do cliente.
Quando o contêiner se desconecta, o gerenciador de conexões tenta obter um novo token do TokenProvider antes de se reconectar ao contêiner. Enquanto a rede estiver desconectada, a solicitação de obtenção de API feita falhará fetchOrdererToken
e lançará um erro não retentável. Isso, por sua vez, faz com que o contêiner seja descartado e não seja capaz de se reconectar, mesmo que uma conexão de rede seja restabelecida.
Uma possível solução para esse problema de desconexão é armazenar tokens válidos em cache em Window.localStorage. Com o cache de tokens, o contêiner recuperará um token armazenado válido em vez de fazer uma solicitação de API get enquanto a rede estiver desconectada. Observe que um token armazenado localmente pode expirar após um determinado período de tempo e você ainda precisará fazer uma solicitação de API para obter um novo token válido. Nesse caso, seria necessário um tratamento de erro adicional e uma lógica de repetição para impedir que o contêiner fosse descartado após uma única tentativa com falha.
A forma como você escolhe implementar essas melhorias depende completamente de você e dos requisitos do seu aplicativo. Observe que, com a solução de localStorage
token, você também verá melhorias de desempenho em seu aplicativo porque está removendo uma solicitação de rede em cada getContainer
chamada.
O cache de tokens com algo parecido localStorage
pode vir com implicações de segurança, e fica a seu critério ao decidir qual solução é apropriada para seu aplicativo. Independentemente de implementar ou não o cache de tokens, você deve adicionar a lógica de tratamento de erros e repetição para fetchOrdererToken
fetchStorageToken
que o contêiner não seja descartado após uma única chamada com falha. Considere, por exemplo, envolver a chamada em getToken
um try
bloco com um catch
bloco que tenta novamente e lança um erro somente após um número especificado de tentativas.