Apache Superset 集成实战:Embedded SDK + Guest Token + OAuth2/SSO 完整方案

摘要

在企业业务系统中接入 Apache Superset,真正需要解决的通常不只是"如何展示一个 Dashboard",还包括认证、授权、数据隔离、单点登录、跨域、安全策略以及后续运维等问题。

实际项目中,Superset 集成通常存在两类场景:

  1. 普通业务用户查看报表:用户不离开业务系统,通过 Superset Embedded SDK 将 Dashboard 直接嵌入业务页面,并由业务后端签发短期 Guest Token。
  2. 管理员进入 Superset 控制台:管理员从业务系统跳转到 Superset,通过 OAuth2/SSO 建立 Superset 登录会话,再进行 Dashboard、Chart、Dataset 等配置。

这两个场景虽然都和 Superset 登录有关,但实现链路完全不同。本文将分别介绍两套方案,并重点说明 Guest Token 权限校验、RLS 行级数据隔离、Allowed Domains、CSP、Cookie、服务账号以及常见问题排查。

标签

Apache Superset Java Vue3 OAuth2 SSO Guest Token Embedded SDK RLS 数据可视化 多租户


1. 先说结论:两条链路不要混在一起

Superset 集成最容易出现的问题之一,就是把"普通用户看报表"和"管理员登录 Superset"当成同一个问题处理。

实际上,两者应该拆开。

1.1 普通用户查看报表

推荐链路:

text 复制代码
业务用户
   ↓
业务前端
   ↓
业务后端
   ↓
Superset Guest Token API
   ↓
Embedded SDK
   ↓
Dashboard

特点:

  • 用户只需要登录业务系统。
  • 不需要创建 Superset 普通用户账号。
  • 浏览器拿不到 Superset 管理员账号、密码或 Access Token。
  • 可以在业务后端统一做菜单权限、租户权限、组织权限和数据权限控制。

1.2 管理员进入 Superset 控制台

推荐链路:

text 复制代码
管理员
   ↓
业务系统
   ↓
Superset /login/{provider}
   ↓
业务平台 OAuth2
   ↓
Superset Session
   ↓
Superset 控制台

特点:

  • 用于真正进入 Superset 后台。
  • 适合 Dashboard 设计、Chart 编辑、Dataset 管理等场景。
  • 通过 OAuth2/SSO 避免再次输入 Superset 用户名和密码。

因此可以简单理解为:

看报表用 Guest Token,进后台用 OAuth2/SSO。


2. 整体架构

#mermaid-svg-dL7xzeQoqwkx1yrT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dL7xzeQoqwkx1yrT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dL7xzeQoqwkx1yrT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dL7xzeQoqwkx1yrT .error-icon{fill:#552222;}#mermaid-svg-dL7xzeQoqwkx1yrT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dL7xzeQoqwkx1yrT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dL7xzeQoqwkx1yrT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dL7xzeQoqwkx1yrT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dL7xzeQoqwkx1yrT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dL7xzeQoqwkx1yrT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dL7xzeQoqwkx1yrT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dL7xzeQoqwkx1yrT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dL7xzeQoqwkx1yrT .marker.cross{stroke:#333333;}#mermaid-svg-dL7xzeQoqwkx1yrT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dL7xzeQoqwkx1yrT p{margin:0;}#mermaid-svg-dL7xzeQoqwkx1yrT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dL7xzeQoqwkx1yrT .cluster-label text{fill:#333;}#mermaid-svg-dL7xzeQoqwkx1yrT .cluster-label span{color:#333;}#mermaid-svg-dL7xzeQoqwkx1yrT .cluster-label span p{background-color:transparent;}#mermaid-svg-dL7xzeQoqwkx1yrT .label text,#mermaid-svg-dL7xzeQoqwkx1yrT span{fill:#333;color:#333;}#mermaid-svg-dL7xzeQoqwkx1yrT .node rect,#mermaid-svg-dL7xzeQoqwkx1yrT .node circle,#mermaid-svg-dL7xzeQoqwkx1yrT .node ellipse,#mermaid-svg-dL7xzeQoqwkx1yrT .node polygon,#mermaid-svg-dL7xzeQoqwkx1yrT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dL7xzeQoqwkx1yrT .rough-node .label text,#mermaid-svg-dL7xzeQoqwkx1yrT .node .label text,#mermaid-svg-dL7xzeQoqwkx1yrT .image-shape .label,#mermaid-svg-dL7xzeQoqwkx1yrT .icon-shape .label{text-anchor:middle;}#mermaid-svg-dL7xzeQoqwkx1yrT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dL7xzeQoqwkx1yrT .rough-node .label,#mermaid-svg-dL7xzeQoqwkx1yrT .node .label,#mermaid-svg-dL7xzeQoqwkx1yrT .image-shape .label,#mermaid-svg-dL7xzeQoqwkx1yrT .icon-shape .label{text-align:center;}#mermaid-svg-dL7xzeQoqwkx1yrT .node.clickable{cursor:pointer;}#mermaid-svg-dL7xzeQoqwkx1yrT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dL7xzeQoqwkx1yrT .arrowheadPath{fill:#333333;}#mermaid-svg-dL7xzeQoqwkx1yrT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dL7xzeQoqwkx1yrT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dL7xzeQoqwkx1yrT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dL7xzeQoqwkx1yrT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dL7xzeQoqwkx1yrT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dL7xzeQoqwkx1yrT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dL7xzeQoqwkx1yrT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dL7xzeQoqwkx1yrT .cluster text{fill:#333;}#mermaid-svg-dL7xzeQoqwkx1yrT .cluster span{color:#333;}#mermaid-svg-dL7xzeQoqwkx1yrT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dL7xzeQoqwkx1yrT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dL7xzeQoqwkx1yrT rect.text{fill:none;stroke-width:0;}#mermaid-svg-dL7xzeQoqwkx1yrT .icon-shape,#mermaid-svg-dL7xzeQoqwkx1yrT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dL7xzeQoqwkx1yrT .icon-shape p,#mermaid-svg-dL7xzeQoqwkx1yrT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dL7xzeQoqwkx1yrT .icon-shape .label rect,#mermaid-svg-dL7xzeQoqwkx1yrT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dL7xzeQoqwkx1yrT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dL7xzeQoqwkx1yrT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dL7xzeQoqwkx1yrT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 查看报表
登录/申请 Guest Token
Guest Token
Guest Token
Embedded SDK
进入控制台
业务用户
业务前端
管理员
业务后端
Superset
Superset Dashboard
Superset OAuth 登录入口
业务平台 OAuth2

整体职责划分建议如下:

模块 职责
业务前端 展示报表列表、初始化 Embedded SDK、跳转 Superset 控制台
业务后端 用户鉴权、报表权限校验、查询 Dashboard UUID、申请 Guest Token
Superset Dashboard、Chart、Dataset、Guest Token、OAuth2 Provider
OAuth2 服务 给 Superset 提供统一身份认证
业务数据库 保存报表编码、Dashboard UUID、状态、分类、权限范围等配置

3. 普通用户嵌入 Dashboard

3.1 为什么不建议直接 iframe

最直接的方案可能是:

html 复制代码
<iframe
  src="https://superset.example.com/superset/dashboard/xxx/"
></iframe>

但单纯 iframe 很快会遇到登录问题。

因为 Superset 并不知道当前业务系统用户是谁。

如果依赖 Superset 自己的登录态,就意味着:

  • 业务系统用户还要再登录一次 Superset;
  • 需要维护第二套账号体系;
  • 用户、角色和权限同步成本增加;
  • iframe 中可能出现登录页;
  • 跨域 Cookie 还会带来额外问题。

因此,普通业务用户查看报表时,更推荐:

text 复制代码
Embedded SDK + Guest Token

核心思想是:

业务系统负责"这个用户能不能看",Superset 负责"把报表展示出来"。


4. Guest Token 完整流程

Superset 业务后端 业务前端 用户 Superset 业务后端 业务前端 用户 #mermaid-svg-1Q7tfm2e6sNHC1YR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1Q7tfm2e6sNHC1YR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1Q7tfm2e6sNHC1YR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1Q7tfm2e6sNHC1YR .error-icon{fill:#552222;}#mermaid-svg-1Q7tfm2e6sNHC1YR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1Q7tfm2e6sNHC1YR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1Q7tfm2e6sNHC1YR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1Q7tfm2e6sNHC1YR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1Q7tfm2e6sNHC1YR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1Q7tfm2e6sNHC1YR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1Q7tfm2e6sNHC1YR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1Q7tfm2e6sNHC1YR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1Q7tfm2e6sNHC1YR .marker.cross{stroke:#333333;}#mermaid-svg-1Q7tfm2e6sNHC1YR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1Q7tfm2e6sNHC1YR p{margin:0;}#mermaid-svg-1Q7tfm2e6sNHC1YR .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1Q7tfm2e6sNHC1YR text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-1Q7tfm2e6sNHC1YR .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-1Q7tfm2e6sNHC1YR .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-1Q7tfm2e6sNHC1YR .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-1Q7tfm2e6sNHC1YR .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-1Q7tfm2e6sNHC1YR #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-1Q7tfm2e6sNHC1YR .sequenceNumber{fill:white;}#mermaid-svg-1Q7tfm2e6sNHC1YR #sequencenumber{fill:#333;}#mermaid-svg-1Q7tfm2e6sNHC1YR #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-1Q7tfm2e6sNHC1YR .messageText{fill:#333;stroke:none;}#mermaid-svg-1Q7tfm2e6sNHC1YR .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1Q7tfm2e6sNHC1YR .labelText,#mermaid-svg-1Q7tfm2e6sNHC1YR .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-1Q7tfm2e6sNHC1YR .loopText,#mermaid-svg-1Q7tfm2e6sNHC1YR .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-1Q7tfm2e6sNHC1YR .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-1Q7tfm2e6sNHC1YR .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-1Q7tfm2e6sNHC1YR .noteText,#mermaid-svg-1Q7tfm2e6sNHC1YR .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-1Q7tfm2e6sNHC1YR .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1Q7tfm2e6sNHC1YR .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1Q7tfm2e6sNHC1YR .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1Q7tfm2e6sNHC1YR .actorPopupMenu{position:absolute;}#mermaid-svg-1Q7tfm2e6sNHC1YR .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-1Q7tfm2e6sNHC1YR .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1Q7tfm2e6sNHC1YR .actor-man circle,#mermaid-svg-1Q7tfm2e6sNHC1YR line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-1Q7tfm2e6sNHC1YR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 打开报表页面查询当前用户可访问报表返回 reportCode、名称等根据 reportCode 申请 Guest Token校验当前用户和报表权限根据 reportCode 查询 Dashboard UUIDPOST /api/v1/security/loginAccess TokenPOST /api/v1/security/guest_token/Guest TokenGuest TokenEmbedded SDK 加载 Dashboardiframe 展示 Dashboard

这里有一个非常重要的安全边界:

text 复制代码
浏览器
  ↓
只能拿 Guest Token

业务后端
  ↓
持有 Superset 服务账号
  ↓
持有 Superset Access Token

不要把 Superset 服务账号、密码或 Access Token 返回给浏览器。


5. 前端集成 Embedded SDK

5.1 安装依赖

bash 复制代码
npm install @superset-ui/embedded-sdk

实际版本需要与目标 Superset 版本联调确认,不建议直接照搬其他项目的版本号。

5.2 前端环境配置

env 复制代码
VITE_SUPERSET_URL=https://superset.example.com

这个地址必须是:

用户浏览器能够直接访问的 Superset 外部地址。

例如:

text 复制代码
https://bi.example.com

而不是只有服务器内部能够访问的:

text 复制代码
http://superset:8088

5.3 Vue / TypeScript 示例

ts 复制代码
import { embedDashboard } from '@superset-ui/embedded-sdk';

async function loadDashboard(
  reportCode: string,
  mountPoint: HTMLElement
) {
  const report = await request.get(
    '/api/reports/superset/report-info',
    {
      params: { reportCode }
    }
  );

  await embedDashboard({
    id: report.data.dashboardUuid,

    supersetDomain: import.meta.env.VITE_SUPERSET_URL,

    mountPoint,

    fetchGuestToken: async () => {
      const response = await request.get(
        '/api/reports/superset/guest-token',
        {
          params: { reportCode }
        }
      );

      return response.data.token;
    },

    dashboardUiConfig: {
      hideTitle: true,
      filters: {
        visible: true,
        expanded: false
      }
    }
  });
}

这里建议前端始终围绕:

text 复制代码
reportCode

与业务后端交互,而不是直接把任意 dashboardUuid 交给后端。


6. Dashboard UUID 不要搞错

Embedded SDK 中:

ts 复制代码
id: dashboardUuid

这里需要使用的是:

Superset Dashboard 的 Embedded UUID。

不要和下面这些值混淆:

  • 业务数据库中的报表主键;
  • Dashboard 数字 ID;
  • URL 中的其他参数;
  • 自己生成的业务编码。

推荐业务系统内部维护一个映射关系:

text 复制代码
reportCode
    ↓
Dashboard Embedded UUID

例如:

text 复制代码
water_usage
    ↓
0f2cxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

这样业务代码不需要到处保存 Superset UUID。


7. 报表配置建议放到业务数据库

实际项目中不建议把所有 Dashboard UUID 写死在 Vue 页面中。

可以建立类似:

text 复制代码
sys_superset_report

的报表配置表。

例如:

字段 说明
report_code 业务系统内部唯一报表编码
superset_uuid Dashboard Embedded UUID
category_code 报表分类编码
name_cn 中文名称
name_en 英文名称
sort_order 排序
status 是否启用
permission_code 可选,业务权限标识

例如:

text 复制代码
report_code     = water_usage
superset_uuid   = 0f2cxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
category_code   = statistics
name_cn         = 用水分析
sort_order      = 10
status          = 1

这样以后 Superset Dashboard 发生调整时,通常只需要修改配置,不需要修改前端源码。


8. 后端申请 Superset Access Token

业务后端首先需要使用专门的 Superset 服务账号调用登录接口。

http 复制代码
POST /api/v1/security/login
Content-Type: application/json

请求示例:

json 复制代码
{
  "username": "${SUPERSET_SERVICE_USERNAME}",
  "password": "${SUPERSET_SERVICE_PASSWORD}",
  "provider": "db"
}

Superset 返回 Access Token 后,业务后端再调用 Guest Token API。

这里建议:

  • 不使用日常管理员账号;
  • 创建专门的 Superset 服务账号;
  • 只给它 Guest Token 相关最小权限;
  • 密码通过环境变量、配置中心或 Secret 管理。

例如:

yaml 复制代码
superset:
  base-url: ${SUPERSET_INTERNAL_URL:https://superset.example.com}
  service-username: ${SUPERSET_SERVICE_USERNAME}
  service-password: ${SUPERSET_SERVICE_PASSWORD}
  connect-timeout: 5s
  response-timeout: 15s

不要在 Git 中提交真实账号密码。


9. 申请 Guest Token

拿到 Access Token 后调用:

http 复制代码
POST /api/v1/security/guest_token/
Authorization: Bearer {access_token}
Content-Type: application/json

请求示例:

json 复制代码
{
  "user": {
    "username": "guest_user_001",
    "first_name": "Guest",
    "last_name": "User"
  },
  "resources": [
    {
      "type": "dashboard",
      "id": "dashboard-embedded-uuid"
    }
  ],
  "rls": []
}

Superset 返回:

json 复制代码
{
  "token": "..."
}

业务后端最终只需要返回给浏览器:

json 复制代码
{
  "token": "superset-guest-token",
  "expiresIn": 300
}

10. Guest Token 接口最重要的不是"能签发",而是"先鉴权"

很多 Superset 集成能正常运行,但安全边界并不完整。

例如后端暴露:

http 复制代码
GET /api/reports/superset/guest-token?dashboardUuid=xxx

如果后端只是拿到 UUID 后直接申请 Guest Token,那么用户可以尝试手动修改:

text 复制代码
dashboardUuid=A

为:

text 复制代码
dashboardUuid=B

只要 B 是一个有效 Dashboard,就存在越权访问风险。

因此,更推荐设计成:

text 复制代码
前端传 reportCode
        ↓
后端根据 reportCode 查询报表配置
        ↓
判断报表是否启用
        ↓
校验当前用户是否有权限
        ↓
校验租户 / 组织 / 区域范围
        ↓
取得 Dashboard UUID
        ↓
申请 Guest Token

后端至少应检查:

  1. 当前用户是否已登录;
  2. 当前用户是否拥有该报表菜单或权限;
  3. 报表配置是否存在;
  4. 报表是否启用;
  5. 当前租户是否允许访问;
  6. 当前组织、区域或项目是否允许访问;
  7. UUID 必须来自服务端报表配置,而不是完全信任前端。

一句话总结:

Dashboard UUID 是 Superset 资源标识,不应该直接当成业务权限凭证。


11. 多租户项目一定要考虑 RLS

如果 Guest Token 中:

json 复制代码
"rls": []

表示当前方案只限制了:

这个用户可以访问哪个 Dashboard。

但并没有限制:

这个用户可以看到 Dashboard 中的哪些数据。

例如同一个 Dataset 中包含:

text 复制代码
tenant-001
tenant-002
tenant-003

如果多个租户共用一个 Dashboard,而没有行级权限,那么不同租户之间可能看到不属于自己的数据。

因此,多租户场景建议使用 RLS。

示例:

json 复制代码
{
  "resources": [
    {
      "type": "dashboard",
      "id": "dashboard-uuid"
    }
  ],
  "rls": [
    {
      "dataset": 42,
      "clause": "tenant_id = 'tenant-001'"
    }
  ],
  "user": {
    "username": "guest_user_001"
  }
}

最终效果类似:

sql 复制代码
SELECT *
FROM meter_data
WHERE tenant_id = 'tenant-001';

12. RLS 的值必须来自可信后端上下文

RLS 本身并不等于绝对安全。

如果后端这样写:

java 复制代码
String tenantId = request.getParameter("tenantId");

String clause =
    "tenant_id = '" + tenantId + "'";

那么用户仍然可以修改请求参数。

更加合理的流程应该是:

text 复制代码
当前登录用户
   ↓
SecurityContext
   ↓
TenantContext / OrganizationContext
   ↓
获取可信 tenantId
   ↓
生成 RLS

也就是说:

租户、组织、区域、项目等数据权限值,应该来自后端认证上下文,而不是直接相信浏览器输入。

对于 RLS 条件,也建议:

  • 做白名单校验;
  • 避免直接拼接任意用户输入;
  • 明确 Dataset ID;
  • 检查 Dashboard 内所有 Chart 是否都使用了受控 Dataset。

13. 管理员进入 Superset 控制台:OAuth2/SSO

Guest Token 很适合看 Dashboard,但不适合真正进入 Superset 控制台。

如果管理员需要:

  • 创建 Dashboard;
  • 修改 Chart;
  • 配置 Dataset;
  • 进入 SQL Lab;
  • 管理 Superset 内部对象;

就需要建立 Superset 正常登录 Session。

此时可以使用:

text 复制代码
OAuth2 / SSO

14. OAuth2/SSO 登录流程

业务 OAuth2 服务 Superset 业务前端 管理员 业务 OAuth2 服务 Superset 业务前端 管理员 #mermaid-svg-ITLbTx64V7BfGbzq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ITLbTx64V7BfGbzq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ITLbTx64V7BfGbzq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ITLbTx64V7BfGbzq .error-icon{fill:#552222;}#mermaid-svg-ITLbTx64V7BfGbzq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ITLbTx64V7BfGbzq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ITLbTx64V7BfGbzq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ITLbTx64V7BfGbzq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ITLbTx64V7BfGbzq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ITLbTx64V7BfGbzq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ITLbTx64V7BfGbzq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ITLbTx64V7BfGbzq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ITLbTx64V7BfGbzq .marker.cross{stroke:#333333;}#mermaid-svg-ITLbTx64V7BfGbzq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ITLbTx64V7BfGbzq p{margin:0;}#mermaid-svg-ITLbTx64V7BfGbzq .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ITLbTx64V7BfGbzq text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ITLbTx64V7BfGbzq .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ITLbTx64V7BfGbzq .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ITLbTx64V7BfGbzq .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ITLbTx64V7BfGbzq .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ITLbTx64V7BfGbzq #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ITLbTx64V7BfGbzq .sequenceNumber{fill:white;}#mermaid-svg-ITLbTx64V7BfGbzq #sequencenumber{fill:#333;}#mermaid-svg-ITLbTx64V7BfGbzq #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ITLbTx64V7BfGbzq .messageText{fill:#333;stroke:none;}#mermaid-svg-ITLbTx64V7BfGbzq .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ITLbTx64V7BfGbzq .labelText,#mermaid-svg-ITLbTx64V7BfGbzq .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ITLbTx64V7BfGbzq .loopText,#mermaid-svg-ITLbTx64V7BfGbzq .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ITLbTx64V7BfGbzq .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ITLbTx64V7BfGbzq .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ITLbTx64V7BfGbzq .noteText,#mermaid-svg-ITLbTx64V7BfGbzq .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ITLbTx64V7BfGbzq .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ITLbTx64V7BfGbzq .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ITLbTx64V7BfGbzq .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ITLbTx64V7BfGbzq .actorPopupMenu{position:absolute;}#mermaid-svg-ITLbTx64V7BfGbzq .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ITLbTx64V7BfGbzq .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ITLbTx64V7BfGbzq .actor-man circle,#mermaid-svg-ITLbTx64V7BfGbzq line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ITLbTx64V7BfGbzq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 点击 Superset 控制台打开 /login/businessAuthorization Code 授权请求Authorization CodeCode 换 TokenAccess Token / UserInfo创建 Superset Session进入 Superset 控制台

最终用户体验:

text 复制代码
登录业务系统
   ↓
点击"Superset 控制台"
   ↓
自动完成 OAuth2
   ↓
进入 Superset

15. Superset OAuth Provider

例如业务前端打开:

text 复制代码
https://superset.example.com/login/business

这里的:

text 复制代码
business

需要和 Superset 服务端配置的 OAuth Provider 名称对应。

也就是说:

text 复制代码
/login/business

中的 business 不是随便写的。

Superset 侧通常需要配置:

  • client_id;
  • client_secret;
  • authorization endpoint;
  • token endpoint;
  • user info endpoint;
  • scope;
  • 用户信息映射;
  • Superset Role 映射。

OAuth2 服务端则需要:

  • 创建 Superset OAuth2 Client;
  • 配置允许的 redirect_uri;
  • 使用 Authorization Code 流程;
  • 校验 state;
  • 保证授权码一次性使用。

具体参数需要以实际 Superset 版本和业务 OAuth2 实现为准。


16. 直接打开指定 Dashboard 编辑页面

当管理员已经拥有 Superset Session 后,可以直接打开:

ts 复制代码
window.open(
  `${SUPERSET_BASE_URL}/superset/dashboard/${uuid}/`,
  '_blank'
);

如果 Superset Session 还存在,会直接进入 Dashboard 页面。

如果 Session 不存在,则应该先进入 Superset 登录链路,再通过 OAuth2/SSO 建立会话。


17. Superset Embedded 必要配置

17.1 开启 Embedded 功能

常见配置形式:

python 复制代码
FEATURE_FLAGS = {
    "EMBEDDED_SUPERSET": True
}

部分容器环境也可能通过对应环境变量开启。

具体配置项可能随 Superset 版本变化,应以实际部署版本为准。


17.2 Allowed Domains

Dashboard 开启 Embedded 后,需要设置允许嵌入它的业务域名。

例如:

text 复制代码
https://business.example.com

生产环境不建议配置任意来源。

如果浏览器出现:

text 复制代码
Refused to frame

或:

text 复制代码
403

Allowed Domains 是第一批需要检查的配置之一。


18. Guest Role 和最小权限原则

嵌入场景建议使用专门的 Guest Role。

例如:

python 复制代码
GUEST_ROLE_NAME = "EmbeddedGuest"

权限应该尽可能收敛,只允许:

  • 查看需要的 Dashboard;
  • 读取对应 Chart;
  • 读取对应 Dataset;
  • 访问必要 Database;
  • 使用 Embedded 相关接口。

不要为了"先跑起来"直接给 Guest 或服务账号过大的权限。


19. 前端地址和后端地址可以不同

实际部署中,经常会存在:

text 复制代码
浏览器访问地址:
https://bi.example.com

而业务后端访问:

text 复制代码
http://superset:8088

这是完全合理的。

例如:

text 复制代码
Browser
   ↓
https://bi.example.com
   ↓
Nginx
   ↓
Superset

业务后端则可能:

text 复制代码
Business Backend
   ↓
http://superset:8088

因此配置时可以拆成:

前端:

env 复制代码
VITE_SUPERSET_URL=https://bi.example.com

后端:

yaml 复制代码
superset:
  base-url: http://superset:8088

唯一需要保证的是:

两个地址最终访问的是同一个 Superset 实例,或者同一套 Superset 集群。


Superset 嵌入出现白屏、403、Refused to frame 时,通常需要同时排查多个层面。

20.1 Allowed Domains

确认当前业务前端域名已经加入 Dashboard Embedded Allowed Domains。

20.2 Content-Security-Policy

重点检查:

text 复制代码
Content-Security-Policy

尤其是:

text 复制代码
frame-ancestors

20.3 X-Frame-Options

检查是否存在:

text 复制代码
DENY

或:

text 复制代码
SAMEORIGIN

20.4 Nginx / 网关

有时 Superset 配置本身没有问题,但 Nginx 或 API Gateway 又覆盖了一层响应头。

因此需要同时查看浏览器最终收到的 Header。

如果某些跨站场景依赖 Superset Session Cookie,需要考虑:

python 复制代码
SESSION_COOKIE_SAMESITE = "None"
SESSION_COOKIE_SECURE = True

SameSite=None 通常需要和 HTTPS 一起使用。

不建议为了临时解决 iframe 问题直接:

  • 关闭全部 CSP;
  • 关闭 CSRF;
  • 允许所有 Origin;
  • 放宽所有 Cookie 安全属性。

这样虽然可能暂时能打开页面,但会带来新的安全风险。


21. 推荐的项目目录划分

21.1 前端

text 复制代码
frontend/
├── api/
│   └── superset.ts
├── views/
│   └── reports/
│       └── index.vue
├── components/
│   └── SupersetDashboard.vue
└── views/
    └── sso/
        └── SupersetSsoCallback.vue

21.2 后端

text 复制代码
backend/
├── controller/
│   └── SupersetReportController
├── service/
│   └── SupersetGuestTokenService
├── entity/
│   └── ReportConfig
├── security/
│   └── ReportPermissionService
└── config/
    └── SupersetProperties

职责划分:

模块 职责
ReportConfig 保存业务报表编码与 Superset UUID
ReportPermissionService 校验用户、租户、组织、区域权限
SupersetGuestTokenService 登录 Superset 并申请 Guest Token
SupersetDashboard 前端 Embedded SDK 封装
SupersetSsoCallback 管理员 OAuth2/SSO 登录处理

22. 推荐联调顺序

Superset 集成涉及的环节比较多。

如果所有问题一起排查,很容易把:

text 复制代码
网络
Guest Token
权限
iframe
CORS
CSP
Cookie
OAuth2

混在一起。

推荐按下面顺序逐层验证。

第一步:浏览器能否访问 Superset

text 复制代码
Browser
   ↓
Superset External URL

先确认基础网络没问题。

第二步:业务后端能否访问 Superset

text 复制代码
Business Backend
   ↓
Superset Internal URL

第三步:服务账号登录

测试:

http 复制代码
POST /api/v1/security/login

确认可以拿到 Access Token。

第四步:申请 Guest Token

测试:

http 复制代码
POST /api/v1/security/guest_token/

确认接口正常。

第五步:检查 Dashboard

确认:

  • Dashboard 已发布;
  • 已开启 Embedded;
  • Embedded UUID 正确;
  • Allowed Domains 正确。

第六步:调前端 Embedded SDK

确认:

text 复制代码
Embedded SDK
   ↓
Guest Token
   ↓
Dashboard

能正常加载。

第七步:验证权限

重点测试:

  • 无权限用户是否能访问;
  • 不同租户是否能看到对方数据;
  • 不同组织/区域账号是否满足数据隔离;
  • RLS 是否绑定正确 Dataset。

第八步:最后再调 OAuth2/SSO

管理员控制台登录放到最后。

这样排查起来会清晰很多。


23. 常见问题排查

23.1 Guest Token 返回 401 / 403

重点检查:

  • Superset 服务账号用户名和密码;
  • /api/v1/security/login 是否成功;
  • Access Token 是否正确;
  • Authorization: Bearer ... 是否正确;
  • 服务账号是否有 Guest Token 签发权限;
  • Embedded 功能是否开启。

23.2 Guest Token 已返回,但 iframe 白屏或 403

重点检查:

  • 是否使用 Embedded Dashboard UUID;
  • Dashboard 是否已经发布;
  • 是否开启 Embedded;
  • Allowed Domains 是否包含业务系统 Origin;
  • 前端和后端是否指向同一个 Superset;
  • 浏览器 Console 是否存在 CSP 错误;
  • Network 中是否存在 401、403 或 Cookie 错误。

排查时建议直接打开:

text 复制代码
F12
  ↓
Console
  ↓
Network

浏览器最终收到的错误通常比后端日志更直观。


23.3 Refused to frame

重点排查:

text 复制代码
Content-Security-Policy
X-Frame-Options
frame-ancestors
Allowed Domains

另外不要忘记检查:

text 复制代码
Superset
   ↓
Nginx
   ↓
Gateway

是否有某一层覆盖了响应头。


23.4 Superset 控制台不停跳登录页

重点检查:

  • OAuth Provider 是否存在;
  • Provider 名称是否和 /login/{provider} 一致;
  • Client ID 是否一致;
  • Client Secret 是否一致;
  • redirect_uri 是否完全一致;
  • redirect_uri 是否加入白名单;
  • state 是否被正确保留和校验;
  • Superset Session Cookie 是否被浏览器阻止。

23.5 用户可以看到其他租户的数据

这个问题通常不是 iframe 问题,而是:

数据权限问题。

检查:

  • Guest Token 是否传了 RLS;
  • RLS 是否绑定正确 Dataset ID;
  • tenantId 是否来自可信认证上下文;
  • Dashboard 中是否引用了其他未受限制的 Dataset;
  • 某些 Chart 是否绕过了预期数据集。

24. 几个最容易踩的坑

24.1 Guest Token 和 OAuth2/SSO 不要混用

普通用户:

text 复制代码
Embedded SDK + Guest Token

管理员:

text 复制代码
OAuth2 / SSO

两者解决的是不同问题。


24.2 不要把 Superset 服务账号暴露到前端

错误:

text 复制代码
Browser
   ↓
Superset username/password
   ↓
Superset

推荐:

text 复制代码
Browser
   ↓
Business Backend
   ↓
Superset Service Account
   ↓
Superset

24.3 不要完全相信 dashboardUuid

推荐:

text 复制代码
reportCode
   ↓
后端查配置
   ↓
校验权限
   ↓
取得 UUID
   ↓
Guest Token

24.4 Dashboard 权限不等于数据权限

text 复制代码
允许访问 Dashboard

只解决:

text 复制代码
能不能打开这个报表

并不等于:

text 复制代码
能看到哪些数据

多租户系统还需要考虑 RLS。


24.5 不要在生产 YAML 中写真实密码

以下内容都不应该直接提交 Git:

  • Superset 服务账号密码;
  • OAuth2 Client Secret;
  • SUPERSET_SECRET_KEY;
  • Guest Token 相关密钥。

应统一放到:

  • 环境变量;
  • 配置中心;
  • Kubernetes Secret;
  • Vault;
  • 企业密钥管理服务。

25. 最终推荐架构

text 复制代码
                         ┌──────────────┐
                         │   业务用户    │
                         └──────┬───────┘
                                │
                                ↓
                        ┌───────────────┐
                        │   业务前端     │
                        └───────┬───────┘
                                │
                 ┌──────────────┴──────────────┐
                 │                             │
                 ↓                             ↓
              查看报表                      管理报表
                 │                             │
                 ↓                             ↓
             reportCode                 /login/provider
                 │                             │
                 ↓                             ↓
             业务后端                        Superset
                 │                             │
          用户/报表权限校验                   OAuth2
                 │                             │
          租户/组织权限校验                   ↓
                 │                         业务认证中心
          查询 Dashboard UUID
                 │
             生成 RLS
                 │
          申请 Guest Token
                 │
                 ↓
           Embedded SDK
                 │
                 ↓
              Dashboard

这套方案的职责边界比较清晰。

对普通用户

只需要登录业务系统:

text 复制代码
业务系统
   ↓
Dashboard

无需感知 Superset。

对管理员

可以:

text 复制代码
业务系统
   ↓
OAuth2/SSO
   ↓
Superset 控制台

对安全体系

Superset 服务账号、密码和 Access Token 始终保留在服务端。

业务系统还可以继续叠加:

text 复制代码
菜单权限
+
报表权限
+
租户权限
+
组织权限
+
区域权限
+
RLS 行级数据权限

26. 总结

企业业务系统集成 Superset,本质上不是简单的 iframe 集成,而是围绕以下几个问题设计:

text 复制代码
身份认证
+
业务授权
+
Dashboard 授权
+
数据权限
+
单点登录
+
浏览器安全策略

对于普通业务用户,推荐:

text 复制代码
Embedded SDK
+
Guest Token

对于管理员和报表设计人员,推荐:

text 复制代码
OAuth2
+
SSO

同时,业务后端最好建立:

text 复制代码
reportCode
   ↓
Dashboard UUID

映射,并在 Guest Token 签发前完成业务权限校验。

如果系统存在多租户、多组织、多区域的数据隔离要求,则进一步结合:

text 复制代码
Guest Token RLS

限制 Dataset 行级数据范围。

最终形成:

业务系统负责用户、权限和业务上下文,Superset 专注于数据分析与可视化。

这类职责划分更清晰,也更适合中大型项目长期维护。


参考资料

  • Apache Superset Embedded Dashboard 官方文档
  • Apache Superset Guest Token API
  • Apache Superset Networking / CORS 配置文档
  • Apache Superset Security 配置文档

Superset 不同版本的配置项和接口细节可能存在变化,实际落地时建议以当前部署版本的官方文档为准。

相关推荐
Flynn Liang4 天前
Apache Superset 重要特性详解(三):语义层 / 权限 / 缓存 / 嵌入的实战最佳实践(2026 版)
大数据·clickhouse·数据可视化·架构设计·superset·数据分析平台
源图客11 天前
Apache Flink 2.3.0(最新版本)
大数据·flink·apache
代立冬14 天前
927 个 PR:Apache SeaTunnel 3.0.0 正式发布!AI 时代,数据集成正在迎来新的可能
人工智能·apache
zhongben_keji15 天前
家政小程序开发全解:经营模式、品类矩阵、派单约束与信用核验
线性代数·矩阵·apache
weixin_1772972206916 天前
二手旧物回收小程序先要解决的不是下单,是“上门之后价格变了怎么办“
小程序·apache
努力变的更好16 天前
H5与小程序之间的回调
前端·小程序·apache
十碗饭吃不饱18 天前
Apache POI 踩坑:setFontFamily 二次设置字体不生效、样式残留问题深度解析
java·word·apache
心随_风动19 天前
Ubuntu系统安装Apache服务教程
linux·ubuntu·apache
ly768920 天前
Kafka 消费者重试与死信:Spring for Apache Kafka 的边界设计
spring·kafka·apache·并发消费·消费者重试·死信主题·位移提交