共用方式為


操作說明:使用 Azure 函式撰寫 TokenProvider

注意

此預覽版本是在沒有服務等級協定的情況下提供,不建議用於生產工作負載。 可能不支援特定功能,或可能已經限制功能。

流體架構中,權杖提供者負責建立和簽署權杖,讓 @fluidframework/azure-client 用來向 Azure 流體轉送服務提出要求。 流體架構針對開發目的提供了一個簡單但不安全的 TokenProvider,其名稱為 InsecureTokenProvider。 每個流體服務都必須根據特定服務的驗證和安全性考量因素來實作自訂的 TokenProvider。

您建立的每個 Azure 流體轉送資源都會獲得一個租用戶識別碼及其專屬唯一的租用戶秘密金鑰。 秘密金鑰是共用密碼。 您的應用程式/服務知道,而 Azure 流體轉送服務也知道。 TokenProviders 必須知道用來簽署要求的秘密金鑰,但秘密金鑰不能包含在用戶端程式碼中。

實作 Azure 函式以簽署權杖

建置安全權杖提供者的選項之一是建立 HTTPS 端點,然後建立 TokenProvider 實作,使其對該端點提出經驗證的 HTTPS 要求以擷取權杖。 此方法可讓您將「租用戶秘密金鑰」儲存在安全的位置,例如 Azure Key Vault

完整的解決方案有兩個部分:

  1. 接受要求並傳回 Azure 流體轉送權杖的 HTTPS 端點。
  2. 接受端點 URL,然後對該端點提出要求以擷取權杖的 ITokenProvider 實作。

使用 Azure Functions 建立 TokenProvider 的端點

使用 Azure Functions 是可以快速建立這類 HTTPS 端點的方法。

此範例會示範如何建立您自己的 HTTPTrigger Azure 函式,用它傳入租用戶金鑰以擷取權杖。

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;

@fluidframework/azure-service-utils 套件中的 generateToken 函式會為指定使用者產生一個由租用戶秘密金鑰簽署的權杖。 這個方法可讓權杖在不公開密碼的狀況下傳回給用戶端。 相反地,權杖是在伺服器端用密碼產生的,可提供指定文件的限域存取權。 下方的 ITokenProvider 範例會對此 Azure 函式提出 HTTP 要求以擷取權杖。

部署 Azure Function

Azure Functions 可用數種方式部署。 如需詳細資訊,請參閱 Azure Functions 文件部署一節,以取得更多關於部署 Azure Functions 的資訊。

實作 TokenProvider

TokenProviders 可用許多方式實作,但您必須實作兩個不同的 API 呼叫:fetchOrdererTokenfetchStorageToken。 這些 API 負責分別擷取流體排序器和儲存體服務的權杖。 這兩個函式都會傳回代表權杖值的 TokenResponse 物件。 流體框架執行階段會視需要呼叫這兩個 API 來擷取權杖。 請注意,雖然您的應用程式程式碼只會用一個服務端點來建立與 Azure 流體轉送服務的連線,但在內部與服務結合的 Azure 客戶會將該端點轉譯為排序器和儲存體端點的配對。 工作階段從這時候開始便會使用這兩個端點,而這就是您必須實作兩個不同的函式來擷取權杖的原因。

為了確保租用戶秘密金鑰的安全,金鑰會儲存在安全的後端位置,而且只能在 Azure 函式中存取。 若要擷取權杖,您必須對已部署的 Azure 函式提出 GETPOST 要求,並提供 tenantIDdocumentIduserID/userName。 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 取得新的權杖。 當網路中斷連線時,fetchOrdererToken 中提出的 API 取得要求將會失敗,並擲回無法重試的錯誤。 這反過來又會導致容器被棄置,即使重新建立網路連線,也無法重新連線。

此中斷連線問題的潛在解決方案是在 window.localStorage 快取有效的權杖。 使用權杖快取時,容器會擷取有效儲存的權杖,而不是在網路中斷連線時提出 API 取得要求。 請注意,本機儲存的權杖可能會在一段時間後過期,而且您仍然需要提出 API 要求以取得新的有效權杖。 在此情況下,需要額外的錯誤處理和重試邏輯,以防止容器在單一嘗試失敗之後被棄置。

您選擇實作這些改進的方式完全由您和應用程式的需求決定。 請注意,使用 localStorage 權杖解決方案時,您也會看到應用程式中的效能改善,因為您正在移除每個 getContainer 呼叫的網路要求。

使用類似 localStorage 的權杖快取可能會帶來安全性影響,而您可以自行決定哪種解決方案適合您的應用程式。 無論您是否實作權杖快取,都應該在 fetchOrdererTokenfetchStorageToken 中新增錯誤處理和重試邏輯,以免容器在單一失敗呼叫之後被棄置。 例如,使用只會在指定的重試次數之後重試和擲回錯誤的 catch 區塊,將 getToken 的呼叫包裝在 try 區塊中。

另請參閱