为嵌入的 Looker 内容实现行级安全性

作者:Christopher Seymour(高级数据分析师)和开发者关系工程师 Dean Hicks

简介

Looker 的嵌入功能是 Looker 产品最强大且最有价值的功能之一。如果您正在阅读本指南,则可能您已将 Looker 内容嵌入到您的应用中,或者打算在不久的将来执行此操作。

本指南旨在帮助您更好地了解 Looker 嵌入功能的设计,以便您构建一个功能强大且安全的应用,以便向用户传送数据。作为对这个主题的深入探讨,会有点冗长的阅读。因此请注意,本指南并不是简单地解决问题的快速修复方法,而是帮助您更好地管理整个 Looker 嵌入用例的基础组件。

用例概览

本指南介绍了贵公司在产品中嵌入 Looker 内容的常见用例。

对于这个已签名的嵌入用例,假设您是 Looker 实例的管理员。您在处理两种类型的嵌入式用户:客户(或“品牌用户”)(应该只访问与其公司有关的数据)和帐号所有者(能够访问多个特定客户的数据)。 您的信息中心包含一些图块,您要向使用您产品的每位客户显示这些图块,但您需要为每个客户自动过滤信息中心,以便信息中心仅显示特定于该客户的数据。本文档中的示例使用了两家虚构公司:HooliPied Piper

您有一个名为 products 的表格,其中显示不同品牌的一些商品指标。在已签名的嵌入应用中,每个品牌都对应不同的嵌入用户(具有不同的 external_user_id)。由于每位嵌入用户都应该只能查看自己品牌的数据,因此您可以使用一个简单的“探索”工具,针对 brand 用户属性使用访问权限过滤器:

explore: products {
  access_filter: {
    field: products.brand
    user_attribute: brand
  }
}

您有一个简单的信息中心,该信息中心基于此“探索”,包含两个图块:一个显示品牌名称,另一个显示该品牌的商品数量。

您可以使用 create_sso_embed_url 端点为每个嵌入用户生成此信息中心的嵌入网址。此示例使用了两个品牌:Pied Piper 和 Hooli。以下是您在对 Pied Piper 的 create_sso_embed_url 调用中使用的请求正文,其中包含 external_user_id pied_piper

{
  "target_url": "https://mylookerinstance.cloud.looker.com/embed/dashboards/17",
  "session_length": 300,
  "force_logout_login": true,
  "external_user_id": "pied_piper",
  "first_name": "PiedPiper",
  "last_name": "User",
  "permissions": ["access_data","see_user_dashboards"],
  "models": ["thelook"],
  "user_attributes": {"brand":"Pied Piper"}
}

您为 Pied Piper 生成的网址会按以下方式显示信息中心:

以下是 Hooli 的 create_sso_embed_url 调用中使用的请求正文,其中包含 external_user_id hooli

{
  "target_url": "https://mylookerinstance.cloud.looker.com/embed/dashboards/17",
  "session_length": 300,
  "force_logout_login": true,
  "external_user_id": "hooli",
  "first_name": "Hooli",
  "last_name": "User",
  "permissions": ["access_data","see_user_dashboards"],
  "models": ["thelook"],
  "user_attributes": {"brand":"Hooli"}
}

为 Hooli 生成的网址会按以下方式显示信息中心:

Voilà!系统会根据每个嵌入用户的 brand 用户属性值过滤信息中心。

深入了解

很酷!但是,如果我要向一个帐号所有者授予多个品牌的访问权限,该怎么办?如何确保只有相关用户可以看到我的数据?

好消息!Looker 签名的嵌入功能旨在使开发者能够为用户打造强大的定制数据体验,同时确保遵循数据模型和内容访问政策定义的数据治理。

确保数据治理的气密性对于提供强大的数据体验至关重要。请继续阅读下文,了解一些概念和最佳实践,您在设计最适合自己的体验时可以参考这些概念。首先简单概述所有这一切的工作原理。

Looker 签名嵌入的基础知识

请务必注意,嵌入上下文中的 Looker 用户身份验证和管理方式与在非嵌入上下文中的工作方式基本相同,并且与大多数其他 Web 应用基本相同。

在 Looker 签名的嵌入上下文中,这可能会令人感到困惑,因为已签名的身份验证步骤、用户设置和信息中心本身会组合成一个复杂的长网址。不过,该网址会用于建立会话,即使在将网址缩短后,仍然适用。牢记这个概念,将有助于您成功打造出色的数据体验。

已签名的嵌入网址结构

以下是 create_sso_embed_url 调用生成的一个已签名的嵌入身份验证网址,该网址包含 Pied Piper 的请求正文:

https://mylookerinstance.cloud.looker.com/login/embed/%2Fembed%2Fdashboards%2F17?permissions=%5B%22access_data%22%2C%22see_user_dashboards%22%5D&models=%5B%22thelook%22%5D&signature=iG6vcKBgnA50jaL2iShFeQHwFPN7wvTx7Rz6r%2FtFuvE%3D&nonce=%22967729518a7dbb8a178f1c03a3511dd1%22&time=1696013242&session_length=300&external_user_id=%22pied_piper%22&access_filters=%7B%7D&first_name=%22Pied%22&last_name=%22Piper%22&user_attributes=%7B%22brand%22%3A%22Pied+Piper%22%7D&force_logout_login=true

以下是同一个网址已解码并拆分为单独的行:

https://mylookerinstance.cloud.looker.com/login/embed/
/embed/dashboards/17
?permissions=["access_data","see_user_dashboards"]
&models=["thelook"]
&signature=iG6vcKBgnA50jaL2iShFeQHwFPN7wvTx7Rz6r/tFuvE=
&nonce="967729518a7dbb8a178f1c03a3511dd1"
&time=1696013242
&session_length=300
&external_user_id="pied_piper"
&access_filters={}
&first_name="PiedPiper"
&last_name="User"
&user_attributes={"brand":"Pied Piper"}
&force_logout_login=true

当您访问此网址时,会发生以下情况:

  1. Looker 会查找 external_user_idpied_piper 的现有用户帐号。如果不存在此类账号,Looker 会使用该 external_user_id 创建新的用户帐号。

  2. 现有用户的帐号详细信息(包括权限、模型、群组(如果已指定)、用户属性值(如果已指定)会被网址中指定的帐号详细信息覆盖。

  3. Looker 通过在浏览器中存储会话 Cookie 来对用户进行身份验证,并为其建立会话。

  4. 然后,Looker 会重定向到 create_sso_embed_url 调用中指定的目标网址(即重定向网址):

    https://mylookerinstance.cloud.looker.com/embed/dashboards/17.

    您可以在原始已签名的嵌入网址中,将此重定向网址视为经过编码的相对网址:

    %2Fembed%2Fdashboards%2F17

尽管第 1-3 步会自动在后台进行,并且最终用户看到的所有结果都是最终结果(信息中心本身),但这些步骤与常规的非嵌入 Looker 用户进行身份验证的步骤基本相同。假设您希望某位用户使用用户和密码凭据登录。具体过程如下:

  1. 您(Looker 管理员)前往“Admin - Users”面板,使用搜索栏检查此用户是否已存在用户账号。如果没有,请创建一个新的用户帐号。

  2. 您(Looker 管理员)在“管理 - 用户”面板中点击用户旁边的修改,为用户配置权限、模型、群组、用户属性值和其他值。

  3. 用户前往 https://mylookerinstance.cloud.looker.com/login 的登录页面,输入其用户名和密码。Looker 通过在浏览器中存储会话 Cookie 来对用户进行身份验证,并为其建立会话。

  4. 然后,Looker 会重定向到着陆页(通常为 https://mylookerinstance.cloud.looker.com/browse)。

请注意,会话 Cookie 将应用于浏览器窗口中的每个标签页。如果用户在 https://mylookerinstance.cloud.looker.com/browse 上启动,打开了新的浏览器标签页,并前往其权限授予的任何页面,则页面将使用在原始浏览器标签页中建立的会话 Cookie 按预期加载。

嵌入用户也是如此。嵌入用户在界面中可以访问的页面受到一些限制,他们只能访问前缀为 /embed 的 Look、信息中心和探索网址。不过,他们仍然可以手动导航至其用户帐号详细信息向其授予访问权限的任何信息中心。假设原始已签名嵌入网址在一个浏览器标签页中将您重定向到 https://mylookerinstance.cloud.looker.com/embed/dashboards/17。然后,您打开一个新的浏览器标签页,并加载位于同一个文件夹中(因此具有相同的访问权限限制)的其他嵌入式信息中心:https://mylookerinstance.cloud.looker.com/embed/dashboards/19

虽然在原始的已签名嵌入网址中指定的重定向网址是信息中心 17 的,但如果您在浏览器标签页中手动输入该网址,可以看到信息中心 19 会按预期加载。请注意,加载其他信息中心并不需要其他已签名的嵌入网址。

这里的关键信息是,在该网址中创建的所有用户账号详细信息(权限、用户属性等)将应用于整个用户会话,而不仅仅是应用于原始签名网址中指定的特定信息中心。顾名思义,用户属性是用户的功能,而不是信息中心的功能,并且它们应该用于确定特定用户在整个应用中的访问权限级别,而不只是在某个特定的标签页中。

账号所有者使用情形

与任何可自定义的解决方案一样,您应该避免某些方法。考虑到这一点,请考虑您还有可能拥有或管理多个品牌的帐号所有者。在本例中,帐号所有者同时管理 Pied Piper 和 Hooli 品牌。因此,您的应用会使用之前所示的 create_sso_embed_url 调用中的相同输入,为这两个 external_user_ids 生成网址,并在应用中创建一个新标签页来加载账号所有者想要访问的每个信息中心。我们经常发现开发者会实施这样的解决方案,这会导致用户的工作流程出错:

  1. 前往 Pied Piper 信息中心标签页。
  2. 前往 Hooli 信息中心标签页。
  3. 返回 Pied Piper 信息中心标签页。
  4. 点击 Pied Piper 信息中心内的重新加载按钮。

... Pied Piper 信息中心显示的是 Hooli 数据!

您可以尝试使用类似的方法,但对两个 create_sso_embed_url 调用使用相同的 external_user_id test_user:

{
  "target_url": "https://mylookerinstance.cloud.looker.com/embed/dashboards/17",
  "session_length": 300,
  "force_logout_login": true,
  "external_user_id": "test_user",
  "first_name": "Test",
  "last_name": "User",
  "permissions": ["access_data","see_user_dashboards"],
  "models": ["thelook"],
  "user_attributes": {"brand":"Pied Piper"}
}

{
  "target_url": "https://mylookerinstance.cloud.looker.com/embed/dashboards/17",
  "session_length": 300,
  "force_logout_login": true,
  "external_user_id": "test_user",
  "first_name": "Test",
  "last_name": "User",
  "permissions": ["access_data","see_user_dashboards"],
  "models": ["thelook"],
  "user_attributes": {"brand":"Hooli"}
}

但行为完全相同:当标签页重新加载 Pied Piper 信息中心后,它会显示 Hooli 的数据。

这是怎么回事?如何确保每个品牌的信息中心仅显示该品牌的数据?

从非嵌入角度进行相同方法

如上一部分所述,已签名的嵌入用户会话与常规的非嵌入 Looker 用户会话的工作原理基本相同,因此,在常规的非嵌入 Looker 用户会话的背景下重新定义之前描述的问题方法并分解这些步骤有助于了解如何以更可靠的方式实现此解决方案。如果您向有权访问 Looker 界面的标准 BI 用户提供说明,该工作流将如下所示:

  1. 在“管理 - 用户”页面上创建两个不同的用户帐号。
    1. 在第一个用户帐号的修改页面上,将 brand 用户属性值设置为 pied_piper
    2. 在第二个用户帐号的修改页面上,将 brand 用户属性值设置为 hooli
  2. 您应将两个用户帐号的帐号设置电子邮件发送给帐号所有者。
  3. 帐号所有者会为每个帐号分别设置电子邮件地址和密码凭据。
  4. 您应向账号所有者提供指向信息中心的链接。(https://mylookerinstance.cloud.looker.com/dashboards/17),并告知对方,若要在品牌之间切换信息中心,他们需要返回另一个标签页中的登录页面,输入其他用户账号的电子邮件地址和密码凭据,然后在该标签页中再次加载信息中心。

账号所有者会按照说明操作。但是,在第二个浏览器标签页中输入 Hooli 用户帐号的用户名和密码,并返回到已加载 Pied Piper 信息中心的第一个标签页后,帐号所有者点击了重新加载按钮。帐号所有者感到惊讶,信息中心里显示的是 Hooli 数据!这种情况并不奇怪,因为在嵌入环境中也出现了同样的情况。

您可以通过相同的方式快速细分后一种实现(将同一 external_user_id test_user 与不同的用户属性集结合使用)。该工作流程的等效界面将如下所示:

  1. 您可以为帐号所有者创建一个用户帐号。
  2. 您将 pied_piper 设为该用户帐号的 brand 属性值。
  3. 您告诉帐号所有者,要将信息中心从 Pied Piper 切换到 Hooli,他们需要与您联系,以便您在会话过程中前往该用户的修改页面,将 brand 属性值从 pied_piper 更改为 hooli
  4. 您修改其用户属性后,他们可以在新标签页中再次加载信息中心,以查看 Hooli 数据。

您现在可能在想:

等等...这似乎需要完成大量工作!难道没有办法让他们在信息中心针对每个品牌分别过滤数据吗?

当然可以!这些场景有助于说明一个在非嵌入上下文中已经显而易见的原则,但可能会被嵌入上下文的抽象概念遮挡:单个真人用户应与具有一组用户属性值的单个 Looker 用户帐号相关联。我们的签名嵌入文档中对 external_user_id 的解释也清楚地说明了这一点。

Looker 使用 external_user_id 来区分已签名的嵌入用户,因此必须为每个用户分配唯一 ID。

您可以使用自己喜欢的任何字符串为用户创建 external_user_id,只要字符串对于该用户是唯一的即可。每个 ID 都与一组权限、用户属性和模型相关联。一个浏览器一次只能支持一个 external_user_id(即用户会话)。在会话期间,无法对用户的权限或用户属性进行任何更改。

运用以下最佳实践

将这些原则应用到我们的示例中,您需要单个用户属性值,以授予对账号所有者应在整个应用中有权访问的所有数据的访问权限。为此,您可以为 brand 属性 Pied Piper,Hooli 使用逗号分隔值:

{
  "target_url": "https://mylookerinstance.cloud.looker.com/embed/dashboards/17",
  "session_length": 300,
  "force_logout_login": true,
  "external_user_id": "test_user",
  "first_name": "Test",
  "last_name": "User",
  "permissions": ["access_data","see_user_dashboards"],
  "models": ["thelook"],
  "user_attributes": {"brand":"Pied Piper,Hooli"}
}

为使此语法正常发挥作用,您需要确保将用户属性设置为字符串过滤器(高级)

请注意,如果用户的数据访问权限级别发生变化,您仍然可以为其更改用户属性集。例如,如果帐号所有者拥有第三个品牌,那么您可以将该品牌添加到您为其 brand 用户属性指定的以英文逗号分隔的列表中。这样,当用户退出并重新登录时,所做的更改就会生效。

过滤信息中心结果

好的,我了解到用户属性需要指定用户可以在应用中访问的所有数据。但是,如果我以这种方式指定用户属性,系统会在我的信息中心内显示所有这些品牌的数据!如何将特定信息中心内的结果范围缩小到特定品牌?

过滤特定信息中心的正确方法是使用常规信息中心过滤条件!(这现在看上去可能很明显,但我们发现有些人会卡在用户属性上,而这是在嵌入上下文中应用过滤器的唯一方式 - 可能是因为 user_attributes 是签名嵌入网址中的一个参数,而过滤器不是。)

确保必须提供过滤条件值,并使用单一选择控件选项之一,例如下拉菜单:

请确保已将过滤条件应用于所有必要图块的正确字段:

现在,由于下拉菜单中的可用选项受到用户属性的限制,您的帐号所有者可以在这两个值(并且只能选择这两个值)之间进行选择:

预先填充信息中心过滤条件

好的,现在我们可以按特定品牌过滤信息中心,但我希望当用户在我的应用中加载某个品牌的信息中心时,将过滤条件值设为某个特定品牌。

同样,考虑一下其在非嵌入环境中的运作方式会很有帮助。如何向某人发送已应用特定过滤条件值的信息中心的链接?您可能已经注意到,当您选择过滤条件值时,该过滤条件值会显示在信息中心的网址中:

将网址的该部分添加到 create_sso_embed_url 调用的 target_url 中:

{
  "target_url": "https://mylookerinstance.cloud.looker.com/embed/dashboards/17?Brand=Hooli",
  "session_length": 300,
  "force_logout_login": true,
  "external_user_id": "test_user",
  "first_name": "Test",
  "last_name": "User",
  "permissions": ["access_data","see_user_dashboards"],
  "models": ["thelook"],
  "user_attributes": {"brand":"Pied Piper,Hooli"}
}

如果您使用的是嵌入 SDK,则可以使用 withFilters 指定要应用于嵌入内容的初始过滤条件:

https://looker-open-source.github.io/embed-sdk/classes/EmbedBuilder.html#withFilters

如果您使用的是自己的自定义脚本,则需要确保在对路径进行编码之前向网址添加过滤器。某些值可能已在过滤条件字符串中编码(例如,?Brand=Pied+Piper 中有一个编码为 + 的空格),因此这些值将在最终到达网址中进行了双重编码。您可以参阅“SO 嵌入式信息中心 - 是否将信息中心过滤条件设置为网址的一部分?”关于这些细微差别的 Looker 社区帖子。如果您仍无法应用过滤器,该社区帖子也是添加任何问题的好地方。

隐藏信息中心过滤条件

好的,我已了解如何设置信息中心的初始过滤条件,但我也不希望帐号所有者自行更改信息中心过滤条件 - 过滤条件值只能由帐号所有者在我的应用中转到的信息中心来确定。如何隐藏信息中心过滤条件?

为此,您可以使用主题背景。主题是一项付费功能,因此,如果您尚未在 Looker 实例中启用此功能,则应与 Looker 销售团队联系以启用该功能。

启用该功能后,转到“管理 - 主题”页面的信息中心控件部分,您可以在其中清除显示过滤器栏选项:

然后,您可以将主题设为默认主题,也可以在特定信息中心的网址中应用该主题。同样,这会进入 create_sso_embed_url 调用的 target_url

{
  "target_url": "https://mylookerinstance.cloud.looker.com/embed/dashboards/17?Brand=Hooli&theme=test_theme",
  "session_length": 300,
  "force_logout_login": true,
  "external_user_id": "test_user",
  "first_name": "Test",
  "last_name": "User",
  "permissions": ["access_data","see_user_dashboards"],
  "models": ["thelook"],
  "user_attributes": {"brand":"Pied Piper,Hooli"}
}

如需详细了解如何隐藏嵌入式信息中心过滤条件(包括一些嵌入 SDK 代码段),请参阅 YouTube 教程嵌入带自定义过滤条件的 Looker

最终结果看起来应与原始问题的用户体验完全相同:

但现在,由于过滤条件值已编码到应用中嵌入的各个目标网址中,因此每个品牌的信息中心始终会显示按正确品牌过滤的信息中心,即使在标签页之间来回切换时也是如此。

以其他用户的身份进行 Sudo

现在,用户体验确实非常接近我最初的设想。但在我的用例中,账号所有者拥有的权限和其他用户设置是 external_user_id=pied_piperexternal_user_id=hooli 个人用户所没有的,这导致界面中提供的选项不同,用户体验也略有不同。我希望帐号所有者看到的所有内容与 pied_piper pied_piper 用户看到的完全相同,就好像帐号所有者实际是以这些用户的身份登录的一样。如何才能做到这一点?

您的要求称为“sudo”。如果您希望帐号所有者能够以每个品牌用户的身份进行“模拟”,则您可以在应用中构建类似的 sudo 函数,以便在帐号所有者以 Pied Piper 用户身份模拟用户时为 external_user_id=pied_piper 加载嵌入网址,当帐号所有者以 Hooli 用户模拟身份时,加载 external_user_id=hooli 的嵌入网址。如果您的应用使用了该 API,您还可以通过该 API 以品牌用户的身份通过 login_user API 端点执行 sudo 操作。

但是,如果您再次考虑非嵌入上下文:在“管理 - 用户”页面中,您不能同时以 sudo 作为多个选项卡中的多个非嵌入用户,因为 sudo 会话将像所有其他用户会话一样应用到整个浏览器窗口。因此,您应该将 sudo 设计为一次只让一个用户执行 sudo。而且,仔细想想就会发现,这种设计更加一致,可以完美模仿您模拟用户的体验。例如,pied_piper 用户只能访问 Pied Piper 信息中心,而无权在其他标签页中打开额外的信息中心。因此,以该用户身份模拟他人时,您也应该无法在不同的标签页中打开不同的信息中心。

总结

我们希望本指南对您有所帮助,并且您已做好充分准备,可以继续构建 Looker 签名的嵌入内容! 我们一直致力于将 Looker 打造成最灵活、最强大的嵌入式数据分析产品,并希望听到您的反馈!如果您有疑问或想要了解详情,可以在 Looker 社区中与我们互动,并参加我们的社区活动。