用 Cognito 为 React 中的 QuickSight 单个可视化实现按用户嵌入

2026-09-04 26 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:7 分钟

把整张 BI 仪表板塞进业务系统通常过于笨重。Amazon QuickSight 支持只嵌入其中一个 visual,让趋势图、指标卡或明细表直接出现在 React 页面里。真正需要谨慎设计的部分不是 iframe,而是身份链路:浏览器通过 Amazon Cognito 登录,后端根据已验证的用户生成有范围限制的嵌入 URL,并确保用户只能看到自己有权访问的内容。

身份链路必须由后端闭环

一个典型请求可以沿着下面的路径流动:

  1. 用户通过 Cognito User Pool 登录,React 应用取得 JWT。
  2. React 携带 JWT 请求 API Gateway。
  3. API Gateway 的 Cognito/JWT Authorizer 验证令牌,然后调用 Lambda。
  4. Lambda 从经过验证的 claims 中读取用户标识,映射到对应的 QuickSight 注册用户。
  5. Lambda 调用 QuickSight API,为指定 dashboard 和 visual 生成嵌入 URL。
  6. React 使用 QuickSight Embedding SDK 挂载该 visual。

关键边界是:前端只能提交“想看哪个 visual”,不能提交可信的 UserArn、租户 ID 或权限范围。Lambda 应从 JWT claims 和服务端映射表推导这些值,否则攻击者可以修改请求,冒充其他 QuickSight 用户。

按用户控制访问时,还要区分两层权限:Cognito 回答“这个人是谁”,QuickSight 用户、组、资源共享以及行级安全规则回答“这个人能看什么”。仅仅验证 JWT,并不会自动建立数据隔离。

Lambda 生成受限嵌入 URL

下面是一个可改造的 Node.js Lambda 示例。它假设 API Gateway 已验证 Cognito JWT,并且系统采用可审计的服务端映射函数,把 Cognito sub 转换为 QuickSight 用户 ARN。部署前需要安装 @aws-sdk/client-quicksight,并替换区域、账户、dashboard 和 visual 配置。

npm install @aws-sdk/client-quicksight
// index.mjs
import {
  GenerateEmbedUrlForRegisteredUserCommand,
  QuickSightClient,
} from "@aws-sdk/client-quicksight";

const client = new QuickSightClient({ region: process.env.AWS_REGION });

function quickSightUserArnFor(subject) {
  // 实际项目中应查询 DynamoDB 等服务端映射表,并拒绝未知用户。
  const username = `cognito-${subject}`;
  return `arn:aws:quicksight:${process.env.AWS_REGION}:${process.env.AWS_ACCOUNT_ID}:user/default/${username}`;
}

export const handler = async (event) => {
  const claims = event.requestContext?.authorizer?.jwt?.claims;
  const subject = claims?.sub;
  if (!subject) {
    return { statusCode: 401, body: JSON.stringify({ message: "Unauthorized" }) };
  }

  const requestedVisual = event.pathParameters?.visualId;
  const visualId = process.env.ALLOWED_VISUAL_ID;
  if (requestedVisual !== visualId) {
    return { statusCode: 403, body: JSON.stringify({ message: "Visual not allowed" }) };
  }

  const command = new GenerateEmbedUrlForRegisteredUserCommand({
    AwsAccountId: process.env.AWS_ACCOUNT_ID,
    UserArn: quickSightUserArnFor(subject),
    SessionLifetimeInMinutes: 60,
    ExperienceConfiguration: {
      DashboardVisual: {
        InitialDashboardVisualId: {
          DashboardId: process.env.DASHBOARD_ID,
          VisualId: visualId,
        },
      },
    },
    AllowedDomains: [process.env.APP_ORIGIN],
  });

  const result = await client.send(command);
  return {
    statusCode: 200,
    headers: {
      "content-type": "application/json",
      "cache-control": "no-store",
    },
    body: JSON.stringify({ embedUrl: result.EmbedUrl }),
  };
};

Lambda 执行角色只需要生成嵌入 URL 所需的 QuickSight 权限,不应获得宽泛的管理权限。ALLOWED_VISUAL_IDDASHBOARD_IDAPP_ORIGIN 可以作为 CloudFormation 参数或 Lambda 环境变量放进同一个栈中。生产环境如果支持多个 visual,应维护服务端白名单,例如 visualId -> dashboardId -> requiredRole,而不是直接接受任意资源 ID。

React 只负责取 URL 和挂载 visual

前端安装 QuickSight Embedding SDK:

npm install amazon-quicksight-embedding-sdk

下面的组件假设 getAccessToken() 返回当前 Cognito access token,并且后端暴露 GET /embed/visuals/:visualId。若你的 Authorizer 使用 ID token,应根据现有认证约定调整令牌类型。

import { useEffect, useRef } from "react";
import { createEmbeddingContext } from "amazon-quicksight-embedding-sdk";
import { getAccessToken } from "./auth";

export function RevenueVisual() {
  const containerRef = useRef(null);

  useEffect(() => {
    let disposed = false;

    async function mountVisual() {
      const token = await getAccessToken();
      const response = await fetch("/api/embed/visuals/revenue-by-region", {
        headers: { Authorization: `Bearer ${token}` },
        cache: "no-store",
      });

      if (!response.ok) {
        throw new Error(`Embed URL request failed: ${response.status}`);
      }

      const { embedUrl } = await response.json();
      if (disposed || !containerRef.current) return;

      const context = await createEmbeddingContext();
      await context.embedVisual(
        {
          url: embedUrl,
          container: containerRef.current,
          width: "100%",
          height: "480px",
          resizeHeightOnSizeChangedEvent: true,
        },
        {
          locale: "zh-CN",
        },
      );
    }

    mountVisual().catch(console.error);
    return () => {
      disposed = true;
      if (containerRef.current) containerRef.current.replaceChildren();
    };
  }, []);

  return <div ref={containerRef} aria-label="区域收入分析" />;
}

不要把嵌入 URL写入 localStorage、日志或 CDN 缓存。页面刷新或会话失效后,应重新向后端申请;后端响应设置 Cache-Control: no-store,并将允许嵌入的域名收紧到实际应用域名。

用单个 CloudFormation 栈收拢部署边界

这套方案适合把 Cognito、API Gateway、Lambda、IAM 角色和配置参数放进一个 CloudFormation 栈。QuickSight 中已有的 dashboard、visual、用户和共享关系可以作为栈参数输入;如果团队也用基础设施即代码管理这些资源,则应明确删除策略,避免销毁应用栈时误删分析资产。

上线前至少检查以下事项:

  • API 路由确实启用了 Cognito 或 JWT Authorizer,Lambda 不接受匿名调用。
  • Cognito sub 到 QuickSight 用户的映射由服务端控制,并处理用户停用和删除。
  • Lambda IAM 权限按账户、命名空间和所需操作收敛。
  • dashboard 已共享给目标 QuickSight 用户或组,数据集行级安全规则经过不同角色验证。
  • visual 和 dashboard ID 使用白名单校验,允许域名不包含不必要的通配符。
  • 不缓存、不记录嵌入 URL,并对生成接口设置限流和审计日志。

把认证、授权和嵌入拆成这三个明确层次后,React 组件会保持简单,安全决策则集中在可审计的 AWS 后端中。这也是按用户嵌入单个 QuickSight visual 时最重要的工程边界。


相关推荐