将 Admin SDK 与 Data Connect 搭配使用

Firebase Admin SDK 是一组服务器库,让您可以从特权环境与 Firebase 进行交互,以执行以下操作:以提升的权限和模拟的凭据对 Firebase Data Connect 服务执行查询和变更操作,以便进行批量数据管理和其他操作。

Admin SDK 为您提供了一个 API,用于在读/写模式和只读模式下调用操作。借助只读操作,您可以放心地实现无法修改数据库中数据的管理功能。

Admin SDK 设置

如需开始在服务器上使用 Firebase Data Connect,您首先需要为 Node.js 安装并设置 Admin SDK

在脚本中初始化 Admin SDK

如需初始化 SDK,请导入 Data Connect 扩展程序并声明您的项目服务 ID 和位置。


import { initializeApp } from 'firebase-admin/app';
import { getDataConnect } from 'firebase-admin/data-connect';

// If you'd like to use OAuth2 flows and other credentials to log in,
// visit https://firebase.google.com/docs/admin/setup#initialize-sdk
// for alternative ways to initialize the SDK.

const app = initializeApp();

const dataConnect = getDataConnect({
    serviceId: 'serviceId',
    location: 'us-west2'
});

设计要与 Admin SDK 搭配使用的查询和变更

鉴于以下考虑因素,Admin SDK 对于运行 Data Connect 操作非常有用。

了解 SDK 和 @auth(level: NO_ACCESS) 操作指令

由于 Admin SDK 以特权运行,因此无论您使用 @auth 指令设置的访问权限级别如何,它都可以执行您的任何查询和突变,包括 NO_ACCESS 级别。

如果您在客户端操作之外,还会在 .gql 源文件中整理管理查询和突变,以便将其导入到管理脚本中,Firebase 建议您标记没有任何授权访问权限级别的管理操作,或者更明确地将其设置为 NO_ACCESS。无论哪种方式,这都能防止从客户端或在其他非特权上下文中执行此类操作。

将 SDK 与 Data Connect 模拟器搭配使用

在原型设计和测试环境中,对本地数据执行数据植入和其他操作可能很有用。借助 Admin SDK,您可以简化工作流程,因为它可以在本地流程中忽略身份验证和授权。(您还可以明确选择通过用户模拟来遵守运营团队的身份验证和授权配置。)

设置 DATA_CONNECT_EMULATOR_HOST 环境变量后,Firebase Admin SDK 会自动连接到 Data Connect 模拟器:

export DATA_CONNECT_EMULATOR_HOST="127.0.0.1:9399"

如需了解详情,请参阅以下主题:

运行管理员操作

Admin SDK 用于对关键数据执行特权操作。

Admin SDK 提供三组 API:

  • 生成的 Admin SDK,这些 SDK 是从 gql 定义生成的类型安全 SDK,与生成客户端 SDK 的方式相同。
  • 用于运行任意 GraphQL 操作的常规接口,您的代码可在其中实现查询和变更,并将其传递给读写 executeGraphql 方法或只读 executeGraphqlRead 方法。
  • 一种用于批量数据操作的专用接口,它不使用通用的 executeGraphql 方法,而是公开用于突变操作的专用方法:insertinsertManyupsertupsertMany

使用生成的 SDK 管理数据

您可以像生成客户端 SDK 一样,根据 gql 定义生成 Admin SDK

生成的 Admin SDK 包含与 gql 定义对应的接口和函数,您可以使用这些接口和函数对数据库执行操作。例如,假设您为歌曲数据库生成了一个 SDK,其中包含一个查询 getSongs

import { initializeApp } from "firebase-admin/app";
import { getSongs } from "@dataconnect/admin-generated";

const adminApp = initializeApp();

const songs = await getSongs(
  { limit: 4 },
  { impersonate: { unauthenticated: true } }
);

或者,如需指定连接器配置,请执行以下操作:

import { initializeApp } from "firebase-admin/app";
import { getDataConnect } from "firebase-admin/data-connect";
import {
  connectorConfig,
  getSongs,
} from "@dataconnect/admin-generated";

const adminApp = initializeApp();
const adminDc = getDataConnect(connectorConfig);

const songs = await getSongs(
  adminDc,
  { limit: 4 },
  { impersonate: { unauthenticated: true } }
);

模拟未经过身份验证的用户

Admin SDK 旨在从可信环境中运行,因此可以不受限制地访问您的数据库。

使用 Admin SDK 运行公开操作时,应避免使用完整的管理员权限(遵循最低权限原则)。您应以模拟用户(请参阅下一部分)或模拟未经身份验证的用户的身份运行该操作。未经身份验证的用户只能运行标记为 PUBLIC 的操作。

在上面的示例中,getSongs 查询是以未经身份验证的用户身份执行的。

模拟用户

您还可以通过在 impersonate 选项中传递部分或全部 Firebase Authentication 令牌来代表特定用户执行操作;至少,您必须在 sub 声明中指定用户的用户 ID。(此值与您可以在 Data Connect GraphQL 操作中引用的 auth.uid 服务器值相同。)

当您模拟用户时,只有在您提供的用户数据通过 GraphQL 定义中指定的身份验证检查后,操作才会成功。

如果您从可公开访问的端点调用生成的 SDK,则至关重要的是,该端点必须要求进行身份验证,并且您必须先验证身份验证令牌的完整性,然后才能使用该令牌来模拟用户。

使用可调用函数 Cloud Functions 时,系统会自动验证身份验证令牌,您可以按以下示例所示的方式使用该令牌:

import { HttpsError, onCall } from "firebase-functions/https";

export const callableExample = onCall(async (req) => {
    const authClaims = req.auth?.token;
    if (!authClaims) {
        throw new HttpsError("unauthenticated", "Unauthorized");
    }

    const favoriteSongs = await getMyFavoriteSongs(
        undefined,
        { impersonate: { authClaims } }
    );

    // ...
});

否则,请使用 Admin SDKverifyIdToken 方法来验证和解码身份验证令牌。例如,假设您的端点实现为纯 HTTP 函数,并且您已使用 authorization 标头将 Firebase Authentication 令牌传递给端点(这是标准做法):

import { getAuth } from "firebase-admin/auth";
import { onRequest } from "firebase-functions/https";

const auth = getAuth();

export const httpExample = onRequest(async (req, res) => {
    const token = req.header("authorization")?.replace(/^bearer\s+/i, "");
    if (!token) {
        res.sendStatus(401);
        return;
    }
    let authClaims;
    try {
        authClaims = await auth.verifyIdToken(token);
    } catch {
        res.sendStatus(401);
        return;
    }

    const favoriteSongs = await getMyFavoriteSongs(
        undefined,
        { impersonate: { authClaims } }
    );

    // ...
});

只有在从安全且非公开访问的环境中执行真正意义上的管理任务(例如数据迁移)时,您才应指定并非来自可验证来源的用户 ID:

// Never do this if end users can initiate execution of the code!
const favoriteSongs = await getMyFavoriteSongs(
  undefined,
  { impersonate: { authClaims } }
);

以不受限的访问权限运行

如果您要执行需要管理员级权限的操作,请从调用中省略 impersonate 参数:

await upsertSong(adminDc, {
  title: songTitle_one,
  instrumentsUsed: [Instrument.VOCAL],
});

以这种方式调用的操作具有对数据库的完整访问权限。如果您有仅用于管理目的的查询或突变,则应使用 @auth(level: NO_ACCESS) 指令定义它们。这样做可确保只有管理员级调用者才能执行这些操作。

使用 executeGraphql 方法管理数据

如果您需要执行未定义 gql 突变或查询的一次性操作,可以使用 executeGraphql 方法或只读 executeGraphqlRead 方法。

模拟未经过身份验证的用户

使用 Admin SDK 运行公开操作时,应避免使用完整的管理员权限(遵循最低权限原则)。您应以模拟用户(请参阅下一部分)或模拟未经身份验证的用户的身份运行该操作。未经身份验证的用户只能运行标记为 PUBLIC 的操作。

// Query to get posts, with authentication level PUBLIC
const queryGetPostsImpersonation = `
    query getPosts @auth(level: PUBLIC) {
        posts {
          description
        }
    }`;

// Attempt to access data as an unauthenticated user
const optionsUnauthenticated: GraphqlOptions<undefined> = {
    impersonate: {
        unauthenticated: true
    }
};

// executeGraphql with impersonated unauthenticated user scope
const gqlResponse = await dataConnect.executeGraphql<UserData, undefined>(queryGetPostsImpersonation, optionsUnauthenticated);

模拟用户

在某些使用情形下,您可能希望脚本代表特定用户,根据有限的凭据修改用户数据。这种方法遵循了最小权限原则。

如需使用此接口,请从遵循 Authentication 令牌格式的自定义 JWT 身份验证令牌中收集信息。另请参阅自定义令牌指南

// Get the current user's data
const queryGetUserImpersonation = `
    query getUser @auth(level: USER) {
        user(key: {uid_expr: "auth.uid"}) {
            id,
            name
        }
    }`;

// Impersonate a user with the specified auth claims
const optionsAuthenticated: GraphqlOptions<undefined> = {
    impersonate: {
        authClaims: {
            sub: 'QVBJcy5ndXJ1'
        }
    }
};

// executeGraphql with impersonated authenticated user scope
const gqlResponse = await dataConnect.executeGraphql<UserData, undefined>(queryGetUserImpersonation, optionsAuthenticated);

// gqlResponse -> { "data": { "user": { "id": "QVBJcy5ndXJ1", "name": "Fred" } } }

使用管理员凭据

如果您要执行需要管理员级权限的操作,请从调用中省略 impersonate 参数:

// User can be publicly accessible, or restricted to admins
const query = "query getProfile(id: AuthID) { user(id: $id) { id name } }";

interface UserData {
  user: {
    id: string;
    name: string;
  };
}

export interface UserVariables {
  id: string;
}

const options:GraphqlOptions<UserVariables> = { variables: { id: "QVBJcy5ndXJ1" } };

// executeGraphql
const gqlResponse = await dataConnect.executeGraphql<UserData, UserVariables>(query, options);

// executeGraphqlRead (similar to previous sample but only for read operations)
const gqlResponse = await dataConnect.executeGraphqlRead<UserData, UserVariables>(query, options);

// gqlResponse -> { "data": { "user": { "id": "QVBJcy5ndXJ1", "name": "Fred" } } }

以这种方式调用的操作具有对数据库的完整访问权限。如果您有仅用于管理目的的查询或突变,则应使用 @auth(level: NO_ACCESS) 指令定义它们。这样做可确保只有管理员级调用者才能执行这些操作。

执行批量数据操作

Firebase 建议您使用 Admin SDK 对生产数据库执行批量数据操作。

该 SDK 提供了以下用于处理批量数据的方法。根据提供的实参,每种方法都会构建并执行 GraphQL 变更。


// Methods of the bulk operations API
// dc is a Data Connect admin instance from getDataConnect

const resp = await dc.insert("movie" /*table name*/, data[0]);
const resp = await dc.insertMany("movie" /*table name*/, data);
const resp = await dc.upsert("movie" /*table name*/, data[0]);
const resp = await dc.upsertMany("movie" /*table name*/, data);

批量操作的性能说明

每次向后端发出请求都会产生一次 Cloud SQL 往返,因此批处理越多,吞吐量就越高。

不过,批次大小越大,生成的 SQL 语句就越长。当达到 PostgreSQL SQL 语句长度限制时,您会遇到错误。

在实践中,您可以通过实验找到适合工作负载的批次大小。

后续步骤