把整张 BI 仪表板塞进业务系统通常过于笨重。Amazon QuickSight 支持只嵌入其中一个 visual,让趋势图、指标卡或明细表直接出现在 React 页面里。真正需要谨慎设计的部分不是 iframe,而是身份链路:浏览器通过 Amazon Cognito 登录,后端根据已验证的用户生成有范围限制的嵌入 URL,并确保用户只能看到自己有权访问的内容。
身份链路必须由后端闭环
一个典型请求可以沿着下面的路径流动:
- 用户通过 Cognito User Pool 登录,React 应用取得 JWT。
- React 携带 JWT 请求 API Gateway。
- API Gateway 的 Cognito/JWT Authorizer 验证令牌,然后调用 Lambda。
- Lambda 从经过验证的 claims 中读取用户标识,映射到对应的 QuickSight 注册用户。
- Lambda 调用 QuickSight API,为指定 dashboard 和 visual 生成嵌入 URL。
- 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_ID、DASHBOARD_ID 和 APP_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 时最重要的工程边界。