Metabase 嵌入安全指南:Public、Guest 与 SSO 三种嵌入方式的认证与授权机制
Metabase 嵌入安全指南Public、Guest 与 SSO 三种嵌入方式的认证与授权机制【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase本文是 Metabase 嵌入功能的安全加固实战指南围绕认证Authentication你是谁与授权Authorization你能访问什么两大概念系统讲解 Public 公开嵌入、Guest 访客嵌入JWT 签名授权以及 Modular / Full-app 嵌入JWT 或 SAML 单点登录三种形态的安全模型。你将掌握每种嵌入方式的数据泄露风险点、锁定参数locked parameters的正确用法、多租户场景下的行列级权限落地方式以及 Metabase 后端源码中 JWT 验签与参数白名单校验的底层实现。认证与授权安全的基础二元组在互联网上保护任何资源都离不开两个基本概念Metabase 的嵌入安全同样建立在这两者之上认证Authentication回答这个人是谁常用的标准包括 JWT 和 SAML。授权Authorization回答这个人能访问什么常用标准如 OAuth 2.0。Metabase 的嵌入安全讨论主要围绕认证展开不同嵌入方式决定了 Metabase 能否以及如何确认请求方的身份进而决定了数据能被谁看到。下面从安全强度最弱的 Public 嵌入开始逐级深入。Public 公开嵌入无认证、无授权Public 公开嵌入 不涉及任何认证或授权。它在 Metabase 中生成一个以唯一字符串结尾的公开链接形如https://my-metabase.com/public/dashboard/184f819c-2c80-4b2d-80f8-26bffaae5d8b链接末尾的字符串示例中为184f819c-2c80-4b2d-80f8-26bffaae5d8b唯一标识了某个 Metabase 问题或仪表板。由于公开嵌入不做任何身份校验任何拿到该 URL 的人都能查看其中的数据。在源码层面公开分享由 public_sharing/validation.clj 中的check-public-sharing-enabled把关只有站点级设置enable-public-sharing为true时才允许访问否则直接返回400 Public sharing is not enabled.。但请注意这个开关只控制是否允许公开分享一旦开启任何拿到链接的人即为授权用户。典型案例Public 链接中的过滤器并不能保护数据设想一个展示 Accounts账户数据的仪表板Account IDPlanStatus1BasicActive2BasicActive3BasicInactive4PremiumInactive5PremiumActive我们希望在嵌入中展示Status Active的过滤视图。在 Public 嵌入中可以通过向公开链接追加查询参数来应用并隐藏过滤器https://my-metabase.com/public/dashboard/184f819c-2c80-4b2d-80f8-26bffaae5d8b?statusactive#hide_parametersstatus其中#hide_parametersstatus让嵌入页面不渲染过滤器控件得到如下过滤结果Account IDPlanStatus1BasicActive2BasicActive5PremiumActive风险就在这里hide_parameters只是隐藏界面过滤逻辑完全由 URL 查询参数决定而查询参数是可以被任何人手动修改的。攻击者只需把嵌入用的公开链接去掉?statusactivehttps://my-metabase.com/public/dashboard/184f819c-2c80-4b2d-80f8-26bffaae5d8b再次加载时Status Active 的过滤条件即被移除攻击者将看到完整的 Accounts 数据包括所有 Inactive 账户行。结论Public 嵌入只适合放行完全公开、无敏感信息的数据任何希望通过过滤参数来限制数据的想法在 Public 嵌入中都不成立。Guest 嵌入用 JWT 签名实现授权但不认证与 Public 嵌入不同Guest 嵌入 通过一套JWT 授权流程解决数据保护问题它做两件事对资源签名如图表或仪表板的 URL确保只有你的嵌入应用可以向 Metabase 请求数据对参数签名如仪表板过滤器防止他人像上文的 Public 示例那样篡改过滤器获取其他数据。Guest 嵌入没有用户会话Guest 嵌入不会在 Metabase 侧认证访问者的身份因此访问者无需创建 Metabase 账户即可查看嵌入内容。但没有账户也意味着 Metabase 无法记住用户或会话由此带来一系列限制Metabase 的权限系统与行列级安全不会生效——如果需要锁住敏感数据必须为每一个Guest 嵌入单独配置锁定参数Guest 嵌入中的过滤器选择会在签名 JWT 过期后重置除非配置了 JWT 刷新让嵌入在令牌过期后无刷新地换入新令牌所有 Guest 嵌入的使用行为都会在使用分析中归入 External user。Guest 嵌入与 Modular / Full-app 嵌入的安全定位差异Guest 嵌入只保证对你的 Metabase 数据的访问是经过授权的由你决定哪些内容可访问。如果你希望基于访问者身份做精细化控制决定谁能访问什么则需要在嵌入应用中自建认证流程手动把用户身份关联到每个 Guest 嵌入的锁定参数上。需要特别注意的是锁定参数本质上是过滤器因此Guest 嵌入只能实现行级row-level的数据限制。如果希望以更省力的方式为不同客户展示不同数据视图且不互相越权则应采用Modular / Full-app 嵌入的 JWT 或 SAML 认证流程。Guest 嵌入的 JWT 授权流程上图展示了签名 JWT 保护嵌入的完整时序访客到达你的前端收到展示 Metabase 嵌入 URL 的请求。签名请求你的后端生成一个携带签名 JWT 的 Metabase 嵌入 URL。签名 JWT 中应编码你用来过滤数据的查询参数。响应Metabase 后端根据签名 JWT 中编码的查询参数返回数据。成功你的前端展示带有正确数据的嵌入 Metabase 页面。可选刷新 / 初始化如果你配置了guestEmbedProviderUri见从服务器刷新或初始化 JWT当当前令牌过期后嵌入需要发起下一次数据请求时例如访客更改了过滤器值嵌入会调用你配置的端点获取新令牌。注意嵌入不会自动获取新令牌该端点也可以在加载时提供首个令牌因此你可以通过拒绝签发新令牌来实现访问撤销。典型示例用锁定参数保护 Guest 嵌入的数据回到上面的 Accounts 示例。在 Public 嵌入中过滤器参数明文写在 URL 上可被任意篡改而在 Guest 嵌入中我们可以把过滤器锁定将 Status Active 配置为锁定参数后?statusactive被编码进签名 JWT从 Guest 嵌入 URL 上不可见、不可编辑https://my-metabase.com/dashboard/your_signed_jwt如果有人试图在 Guest 嵌入 URL 末尾追加一个未签名的查询参数https://my-metabase.com/dashboard/your_signed_jwt?statusinactiveMetabase 会拒绝这个未经授权的数据请求Inactive 账户行依然对嵌入隐藏。从源码看这一行为由 embedding_rest/api/common.clj 中的check-params-are-allowed强制实施。它以仪表板/问题的:embedding_params白名单为依据对每个参数做三态判定disabledtoken 和用户都不允许指定该参数否则报Youre not allowed to specify a value for param.enabledJWT或URL 查询参数可指定其一但不能同时指定否则报You cant specify a value for param if its already set in the JWT.locked参数值必须出现在 JWT 中You must specify a value for param in the JWT.且禁止出现在 URL 查询参数中You can only specify a value for param in the JWT.。正是locked分支保证了追加?statusinactive会被拒绝这一安全语义。此外validate-and-merge-params还会把所有校验通过后的参数合并URL 参数与 JWT 参数共同生效但 JWT 中的值优先。典型示例发送用户属性到锁定参数现在考虑一个更真实的场景向客户开放 Accounts 表让客户按 Account ID 查询自己的行数据。Account IDPlanStatus1BasicActive2BasicActive3BasicInactive4PremiumInactive5PremiumActive如果不想为每个客户创建 Metabase 登录需要准备一个可嵌入的仪表板包含 Accounts 数据一个针对 Account ID 过滤器的锁定参数嵌入应用承载嵌入的 Web 应用中的登录流程。流程大致如下客户登录你的 Web 应用应用后端根据登录邮箱查出该客户的account_id应用后端使用 Metabase 的密钥secret key生成嵌入 URL签发一个签名 JWT把Account ID account_id的查询参数编码进 JWT用于过滤 Accounts 仪表板Metabase 在 Guest 嵌入 URL 上返回过滤后的仪表板应用前端在 iframe 中展示过滤后的仪表板。由于参数被签名锁定客户无法通过改 URL 查看其他 Account ID 的数据。这一模式也常用于驱动你自建的定制过滤器组件因为锁定参数在嵌入中不渲染控件你可以让客户在自建组件中选值再由服务器签发携带新参数值的 JWT 并替换到组件上实现自定义过滤器 安全锁定。Modular / Full-app 嵌入的 SSO 认证与授权Modular 嵌入包括使用 SDK与 Full-app 嵌入 通过 SSOJWT 或 SAML在一次流程中同时完成认证与授权。SSO 集成可以方便地将用户属性如角色、部门映射到细粒度的数据访问级别包括表级权限行级安全按表中某列过滤列级安全通过 SQL 问题创建自定义视图其他数据权限如下载权限、SQL 访问权限。Full-app 嵌入的 SSO 流程上图展示了 Full-app 嵌入如何通过 SSO 保障安全访客到达你的前端收到展示所有内容的请求其中包括 Metabase 组件如 React 组件加载嵌入前端组件通过你的嵌入 URL 加载 Metabase 前端检查会话Metabase 后端在展示嵌入 URL 上的数据前检查是否存在有效会话即已登录的访客若没有有效会话重定向到 SSOMetabase 前端将访客重定向到你的 SSO 登录页SSO 认证你的 SSO 流程认证访客身份并基于其身份生成会话会话信息应编码用户属性如组归属与行列级安全权限重定向回 MetabaseSSO 流程携带会话信息将访客重定向回 Metabase 前端请求Metabase 前端向后端发送数据请求附带会话信息响应Metabase 后端根据会话信息中编码的用户属性返回数据成功你的前端组件为已登录访客展示带有正确数据的嵌入页面。第 4 步的具体机制会因你使用 JWT 还是 SAML 做 SSO 而略有差异。典型示例用 SSO 行列级安全实现多租户隔离在 Guest 嵌入示例中我们用锁定参数为 Accounts 表手动构造安全过滤视图。而 Modular / Full-app 嵌入配合 SSO 的好处是无需为每个嵌入手动维护锁定参数而是把身份提供方IdP的用户属性映射到 Metabase 的权限与行列级安全用户从首次登录起就能被认证并授权访问自己那部分数据。扩展 Accounts 示例加入 Tenant ID租户 ID表示一组客户所属的父组织Tenant IDAccount IDPlanStatus9991BasicActive9992BasicActive9993BasicInactive7774PremiumInactive7775PremiumActive需求升级为单个客户只能查看自己 Account ID 的数据租户可以查看其名下所有子账户但不能看其他租户的数据。配置多租户权限的步骤在 IdP 中创建primary_id属性唯一标识所有租户和客户在 IdP 中为每位使用 Metabase 的人创建用户属性role取值tenant或customer在 Metabase 中创建两个组Tenants 和 Customers在 Metabase 与 IdP 之间同步组成员关系使roletenant的人加入 Tenant 组、rolecustomer的人加入 Customers 组为每个组在 Accounts 表上配置行级安全Customers 组Accounts 表按Account ID primary_id限制Tenants 组Accounts 表按Tenant ID primary_id限制。当 Tenant A 首次通过 SSO 登录时Metabase 为其创建账户IdP 向 Metabase 发送roletenant与primary_id999属性Metabase 自动将 Tenant A 分配到 Tenant 组Tenant A 获得 Tenant 组的权限包括行列级安全Tenant A 在 Metabase 的任何地方都只能看到 Accounts 表的受限视图Tenant IDAccount IDPlanStatus9991BasicActive9992BasicActive9993BasicInactive而 Customer 1 登录后基于其role与primary_id属性看到的是另一份过滤视图Tenant IDAccount IDPlanStatusA1BasicActive对比可见Guest 嵌入的锁定参数是每嵌入手工配置的静态方案而 SSO 方案把身份、组、行列权限打通天然支持不同人登录看到不同数据的动态多租户场景。源码级原理JWT 验签与嵌入配置为了更深入理解上述安全语义可以查看 Metabase 后端的关键实现。JWT 验签实现embedding/jwt.clj 是 Guest 嵌入 JWT 验签的核心check-valid-alg会手动解析 JWT header拒绝alg缺失或alg none的令牌——none算法在标准中合法但对签名安全而言必须禁用否则攻击者可伪造无签名令牌unsign使用embedding-secret-key对令牌验签并允许60 秒时钟偏移leeway以容忍服务器与签发方时钟不同步验签失败会以 400 状态码抛出get-in-unsigned-token-or-throw从解码后的令牌中按路径取值缺少必需字段如[:params]时返回 400。嵌入相关配置项embedding/settings.clj 定义了嵌入功能的后端设置其中与安全直接相关的有embedding-secret-key用于签名/api/embed请求 JWT 的密钥setter 强制要求十六进制编码的 256 位密钥即 64 字符字符串非此格式会直接断言失败enable-embedding-static/enable-embedding-interactive/enable-embedding-sdk/enable-embedding-simple分别控制静态嵌入、交互式嵌入、SDK 嵌入与 Modular 嵌入的开关默认均为false其中旧版MB_ENABLE_EMBEDDING环境变量自 0.51.0 起废弃启动时会被同步到新开关若新旧同时设置会抛出冲突异常embedding-app-origins-sdk/embedding-app-origins-interactive以空格分隔的允许嵌入来源origin白名单localhost:*始终被允许并从存储值中剔除且当DISABLE_CORS_ON_LOCALHOST开启时禁止配置 localhost 来源。另外 embedding_rest/api/embed.clj 是 JWT 嵌入的对外 API 层token 结构为{:resource {:question card-id 或 :dashboard dashboard-id} :params params}并提供卡查询、仪表板查询、导出csv/xlsx/json、参数值搜索、地图瓦片等端点每个端点都会先unsign-and-translate-ids验签再检查对象已启用嵌入check-embedding-enabled-for-card/check-embedding-enabled-for-dashboard未启用返回 400。在参数校验时锁定参数的值还会作为约束传入参数值搜索确保enabled的联动过滤器下拉项始终与锁定值过滤后的数据一致。关于仪表板 / 问题可嵌入性的说明无论采用哪种嵌入方式前提都是把目标问题或仪表板设为可嵌入在 Metabase 中打开对应对象点击Share分享图标并选择Embed嵌入按向导完成配置后点击Publish发布。对 Guest 嵌入而言即使你使用 SDK 方式接入也仍需先在 Metabase 中发布该对象Metabase 才会允许为其提供服务。总结与安全选型建议嵌入方式认证授权适用场景数据保护手段Public 公开嵌入无无拿到链接即有权完全公开的数据展示不可依赖参数过滤只能整块公开Guest 嵌入无应用侧自建登录通过签名 JWT 授权资源与参数无需为访客建账号的受限数据展示锁定参数实现行级限制guestEmbedProviderUri实现令牌刷新与按用户签发Modular / Full-app 嵌入JWT 或 SAML SSO组权限 行列级安全多租户、需要身份识别与细粒度授权的场景IdP 用户属性映射权限天然支持行/列级数据隔离更多进阶内容可继续阅读为不同客户 schema 配置权限。如果你希望从零搭建可运行的嵌入应用仓库的docs/embedding/目录下还提供了 sdk 文档、静态嵌入参数说明以及参数传递等配套指南可作为落地的补充参考。【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考