Bagikan melalui


Cara: Menulis TokenProvider dengan Azure Function

Catatan

Versi pratinjau ini disediakan tanpa perjanjian tingkat layanan, dan tidak disarankan untuk beban kerja produksi. Fitur tertentu mungkin tidak didukung atau mungkin memiliki kemampuan terbatas.

Pada Fluid Framework, TokenProviders bertanggung jawab untuk membuat dan menandatangani token yang digunakan @fluidframework/azure-client untuk membuat permintaan ke layanan Azure Fluid Relay. Fluid Framework menyediakan TokenProvider yang sederhana dan tidak aman untuk tujuan pengembangan, tepat bernama InsecureTokenProvider. Setiap layanan Fluid harus menerapkan TokenProvider kustom berdasarkan pertimbangan autentikasi dan keamanan layanan tertentu.

Setiap sumber daya Azure Fluid Relay yang Anda buat diberi ID penyewa dan kunci rahasia penyewa uniknya sendiri. Kunci rahasia merupakan rahasia bersama. Aplikasi/layanan Anda mengetahuinya, dan layanan Azure Fluid Relay mengetahuinya. TokenProviders harus mengetahui kunci rahasia untuk menandatangani permintaan, tetapi kunci rahasia tidak dapat disertakan dalam kode klien.

Menerapkan Fungsi Azure untuk menandatangani token

Salah satu opsi untuk membangun penyedia token yang aman adalah membuat titik akhir HTTPS dan membuat implementasi TokenProvider yang membuat permintaan HTTPS yang diautentikasi ke titik akhir tersebut mengambil token. Jalur ini memungkinkan Anda menyimpan kunci rahasia penyewa di lokasi yang aman, seperti Azure Key Vault.

Solusi lengkap memiliki dua bagian:

  1. Titik akhir HTTPS yang menerima permintaan dan menghasilkan token Azure Fluid Relay.
  2. Implementasi ITokenProvider yang menerima URL ke titik akhir, lalu membuat permintaan ke titik akhir tersebut untuk mengambil token.

Membuat titik akhir untuk TokenProvider Anda menggunakan Azure Functions

Menggunakan Azure Functions adalah cara cepat untuk membuat titik akhir HTTPS seperti itu.

Contoh ini menunjukkan cara membuat Fungsi Azure HTTPTrigger Anda sendiri yang mengambil token dengan melewati kunci penyewa.

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;

Fungsi generateToken, ditemukan dalam paket @fluidframework/azure-service-utils, menghasilkan token untuk pengguna tertentu yang ditandatangani menggunakan kunci rahasia penyewa. Metode ini memungkinkan token dikembalikan ke klien tanpa mengekspos rahasia. Sebaliknya, token dihasilkan di sisi server menggunakan rahasia tersebut untuk menyediakan akses cakupan ke dokumen tertentu. Contoh ITokenProvider di bawah ini membuat permintaan HTTP ke Fungsi Azure ini untuk mengambil token.

Menggunakan Azure Function

Azure Functions dapat disebarkan dalam beberapa cara. Untuk informasi selengkapnya, lihat bagian Sebarkan dokumentasi Azure Functions untuk informasi selengkapnya tentang menyebarkan Azure Functions.

Menerapkan TokenProvider

TokenProviders dapat diimplementasikan dalam banyak cara, tetapi harus menerapkan dua panggilan API terpisah:fetchOrdererToken dan fetchStorageToken. API ini bertanggung jawab mengambil token untuk pemesan Fluid dan layanan penyimpanan masing-masing. Kedua fungsi menampilkan objek TokenResponse yang mewakili nilai token. Runtime Kerangka Kerja Fluid memanggil kedua API ini sesuai kebutuhan untuk mengambil token. Perhatikan bahwa saat kode aplikasi Anda hanya menggunakan satu titik akhir layanan untuk membangun konektivitas dengan layanan Azure Fluid Relay, klien azure secara internal bersama dengan layanan menerjemahkan bahwa satu titik akhir ke pasangan pemesan dan titik akhir penyimpanan. Kedua titik akhir tersebut digunakan dari titik itu untuk sesi tersebut, itulah sebabnya Anda perlu menerapkan dua fungsi terpisah untuk mengambil token, satu untuk masing-masing.

Untuk memastikan bahwa kunci rahasia penyewa tetap aman, kunci tersebut disimpan di lokasi backend yang aman dan hanya dapat diakses dari dalam Azure Function. Untuk mengambil token, Anda perlu membuat permintaan GET atau POST ke Fungsi Azure yang disebarkan, dengan menyediakan tenantID dan documentId, dan userID/userName. Fungsi Azure bertanggung jawab atas pemetaan antara ID penyewa dan rahasia kunci penyewa untuk membuat dan menandatangani token dengan tepat.

Contoh implementasi di bawah ini menangani pembuatan permintaan ini ke Azure Function Anda. Ini menggunakan pustaka axios untuk membuat permintaan HTTP. Anda dapat menggunakan pustaka atau pendekatan lain untuk membuat permintaan HTTP dari kode server. Implementasi khusus ini juga disediakan untuk Anda sebagai ekspor dari paket @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;
    }
}

Menambahkan efisiensi dan penanganan kesalahan

AzureFunctionTokenProvider adalah implementasi TokenProvider sederhana yang harus diperlakukan sebagai titik awal saat menerapkan penyedia token kustom Anda sendiri. Untuk implementasi penyedia token siap produksi, Anda harus mempertimbangkan berbagai skenario kegagalan yang perlu ditangani penyedia token. Misalnya, AzureFunctionTokenProvider implementasi gagal menangani situasi pemutusan jaringan karena tidak menyimpan token di sisi klien.

Ketika kontainer terputus, manajer koneksi mencoba mendapatkan token baru dari TokenProvider sebelum menyambungkan kembali ke kontainer. Saat jaringan terputus, permintaan get API yang dibuat akan gagal dan melemparkan kesalahan yang tidak dapat diulang fetchOrdererToken . Ini pada gilirannya menyebabkan kontainer dibuang dan tidak dapat terhubung kembali meskipun koneksi jaringan dibuat kembali.

Solusi potensial untuk masalah pemutusan sambungan ini adalah dengan menyimpan token yang valid di Window.localStorage. Dengan penembolokan token, kontainer akan mengambil token tersimpan yang valid alih-alih membuat permintaan dapatkan API saat jaringan terputus. Perhatikan bahwa token yang disimpan secara lokal dapat kedaluwarsa setelah jangka waktu tertentu dan Anda masih perlu membuat permintaan API untuk mendapatkan token baru yang valid. Dalam hal ini, penanganan kesalahan tambahan dan logika coba lagi akan diperlukan untuk mencegah kontainer membuang setelah satu upaya gagal.

Bagaimana Anda memilih untuk menerapkan peningkatan ini sepenuhnya terserah Anda dan persyaratan aplikasi Anda. Perhatikan bahwa dengan localStorage solusi token, Anda juga akan melihat peningkatan performa di aplikasi Karena Anda menghapus permintaan jaringan pada setiap getContainer panggilan.

Penembolokan token dengan sesuatu seperti localStorage mungkin datang dengan implikasi keamanan, dan terserah kebijaksanaan Anda saat memutuskan solusi apa yang sesuai untuk aplikasi Anda. Apakah Anda menerapkan penembolokan token atau tidak, Anda harus menambahkan penanganan kesalahan dan mencoba kembali logika masuk dan fetchStorageToken sehingga kontainer tidak dibuang fetchOrdererToken setelah satu panggilan gagal. Pertimbangkan, misalnya, membungkus panggilan getToken dalam try blok dengan catch blok yang mencoba kembali dan melempar kesalahan hanya setelah sejumlah percobaan ulang yang ditentukan.

Lihat juga