Supabase:列级安全解决方案

Dra*_*nap 11 database postgresql supabase

目前,我正在使用 Supabase 数据库。我面临的一大障碍是列级安全性,这似乎比 RLS 复杂得多。

假设我有一个名为 的列is_banned,该列可查看但不可编辑。但是,其余列应该是可编辑和可查看的。

我真正能想到的唯一解决方案是将其分成两个表,并在“敏感信息”表上使用 RLS - 但为每个表创建一个私有表似乎相当不必要。

还有其他解决方案吗?

tan*_*ius 18

我必须自己处理这个问题。我目前用视图解决这个问题,但将来更愿意选择 RLS 策略、触发器或特权函数(截至目前尚未测试)。我在下面分享我对这个问题的研究笔记。

\n
\n

列级安全性(“CLS”)意味着根据某些条件有选择地禁止列值 UPDATE、INSERT 甚至 SELECT。有几种替代解决方案(摘要),每种方案都有优点和缺点。下面详细讨论它们。

\n

选项 1:RLS 政策

\n

(到目前为止我最喜欢的选项,但我还没有在实践中使用它。)

\n

在这里,对于防止更新的 CLS 策略,您将使用行级安全 (RLS) 策略来检索旧行并比较受保护列的字段值是否会从旧行更改为新行。此问题的候选解决方案已作为Stack Overflow 答案发布,但这仍然需要制作成通用函数。

\n

乍一看,这似乎比触发器更好:它具有相同的优点,此外,Supabase 无论如何都提倡使用 RLS 策略进行访问控制,并且对 RLS 的 UI 支持比触发器好得多。因此,它将通过降低复杂性来提高数据库的一致性和可维护性。

\n

但是,Supabase RLS 编辑器不能用于复杂的 RLS 策略(问题报告),因此作为一种解决方法,应将所有 RLS 代码包装到单个或嵌套函数调用中,或者至少不超过一行代码。更好的是在 Supabase 外部的版本控制下维护 SQL 源代码,并在您想要更改 RLS 策略、表、函数等时将其复制并粘贴到 Supabase SQL 编辑器中。

\n

选项 2:触发器

\n

请参阅此处,了解我的原始说明,以及Github 用户 christophemarois改进的说明。

\n

优点:

\n
    \n
  • 不添加另一个表或视图,因此数据库结构应该由数据决定,而不是由权限系统的怪癖决定。

    \n
  • \n
  • 不需要更改默认的 Supabase 权限或表到架构分配。

    \n
  • \n
  • 结合了 RLS 策略和列级权限的力量。

    \n
  • \n
\n

缺点:

\n
    \n
  • Supabase UI 中尚未很好地支持触发器:只能更改触发器状态,但无法在 UI 中显示或编辑,只能在 PostgreSQL 控制台中显示或编辑。实际上,这并不是什么大问题,因为对于任何现实项目,您都必须直接使用 PostgreSQL 数据库。

    \n
  • \n
  • 它需要 PGSQL 或其他编程语言 \xe2\x80\xa6 的知识,对于某些人来说,我们希望使用 Supabase 避免编程。然而,该解决方案使用一个抽象函数来接收列到白名单(“允许更改”),因此不需要真正的编程,只需部署一些可重用的代码。

    \n
  • \n
\n

选项 3:特权函数

\n
\n

“您可以使用 SECURITY DEFINER 将表隐藏在 FUNCTION 后面。表本身不会提供 UPDATE 访问权限,用户只能通过 FUNCTION 更新表。” (来源

\n
\n

在该函数中,您可以按照您喜欢的任何方式确定列级访问权限。模式中的任何此类函数都public可以通过 API 自动使用:

\n
\n

“编写 PostgreSQL SQL 函数 [\xe2\x80\xa6] 并通过 调用它们supabase.rpc(\'function_name\', {param1: \'value\'});。” (来源)。

\n
\n

但问题是,API 不再具有“所有内容都在表中可用”的统一结构。

\n

选项 4:用户特定的视图

\n

请参阅说明。更多说明:

\n
\n

“您可以创建一个视图来仅显示所需的列,确保使用WHERE语句进行保护,因为它会忽略 RLS(通常),然后使用 RLS 来阻止原始表。” (来源

\n
\n

Supabase 维护者推荐了该解决方案。不过,总的来说,RLS 策略和触发因素似乎更可取。

\n

为了确保此解决方案的安全,您必须使用选项security_barrier = on详细信息),这可能会严重影响视图性能。解决这个问题的唯一方法是不使用WHERE子句,而是通过 重新使用基表的 RLS 策略security_invoker = on。这需要将基表移动到 API 未公开的自定义数据库方案(见下文)。

\n

优点:

\n
    \n
  • 简单的。视图就像表一样,每个人都知道 PostgreSQL 表 \xe2\x80\x93 与触发器或(复杂)RLS 策略的对比。

    \n
  • \n
  • 您会看到您编辑的内容。可以看到表中记录的用户(或其应用程序)不必担心由于 RLS 策略而导致这些记录是否可编辑。用户可以看到任何内容,他们都可以编辑。

    \n
  • \n
  • \xe2\x9d\x93 可根据需要扩展。 (对此仍不确定。)视图中只能提供允许特定用户编辑的列。为了找到正确的列,有时需要更多上下文。没问题:在 API 访问时,根据需要再次连接底层基表中的视图和列。只有代理主键列id需要始终包含在视图中;这不是问题:如果用户尝试编辑它,则只有在使用新值时才能成功,在这种情况下,实际上会创建一条新记录,无论如何,用户可能都可以这样做。(有待确认的是,仍然可以进行具有适当访问保护的更新。)

    \n
  • \n
\n

缺点:

\n
    \n
  • 使桌面空间混乱。理想情况下,API 将以正确的数据库设计中的形式公开数据。通过公开其他视图,API 会变得不必要的复杂。

    \n
  • \n
  • 无法真正复用底层表的RLS策略。security_invoker = on通过在创建视图时使用来完成(详细信息)。然而,在执行此操作时,可以通过视图更新记录的同一用户也可以更新基表中的该记录,从而规避使用视图的列访问限制。解决这个问题的唯一方法是将基表移动到 API 未公开的自定义数据库方案。这是可能的,但增加了更多的结构复杂性。

    \n
  • \n
  • 需要更改默认查看权限。由于这些是简单视图,因此它们是 PostgreSQL 中的“可更新”视图。与 Supabase 模式 public 中默认的表级/视图级权限一起,这意味着所有用户,甚至匿名用户,都可以从这些视图中删除记录,从而导致底层表中的记录被删除。

    \n

    要解决此问题,必须从视图中删除 INSERT 和 DELETE 权限。这是对默认 Supabase 权限的更改,理想情况下是不必要的。

    \n

    还有一种替代解决方案,但不太实用:您可以创建视图以security_invoker = on重用底层表的 RLS 策略。然后使用这些 RLS 策略来防止记录删除。然而,他们必须允许 SELECT 和 UPDATE;因此,除非您将底层表移动到 API 未公开的架构,否则用户将可以绕过为其创建视图的列级安全性。

    \n
  • \n
  • 没有好的方法可以限制 某些用户使用列中的某些值。 这是因为视图不能有自己的 RLS 策略。有几种方法可以解决这个问题:

    \n
      \n
    • 解决这个问题的最佳方法可能是构建表,以便允许对列具有写访问权限的用户使用该列中的每个值。例如,不是列role(user、admin) 和status(applied、approved、disapproved),而是可以为 null 的布尔列user_application, admin_application, user_status, admin_status

      \n
    • \n
    • 对于复杂情况,另一个选择是将基础表移至 API 不可访问的自定义模式(同时仍向所有 Supabase 角色授予使用和权限;请参阅 参考资料),在该基础表上创建 RLS 策略,并重新通过在视图中使用它们security_invoker = on

      \n
    • \n
    • 对于复杂情况,另一种选择是在视图或底层表上使用触发器。

      \n
    • \n
    \n
  • \n
\n

选项 5:列级访问权限

\n
\n

“您只能提供对表的一部分列的 UPDATE 访问:GRANT UPDATE(col1, col2)。(详细信息)”(来源

\n
\n

据报道,维护所有这些访问权限很麻烦。在 Supabase 中,不可能向不同的经过身份验证的用户授予不同的权限,因为所有用户都使用相同的角色访问数据库authenticated。不过,在 PostgREST 级别上工作时,您可以在此处有不同的选项。

\n

选项 6:表拆分

\n

与视图相比,这将主表分成多个部分。使用 RLS 策略,定义谁可以对每个部分表执行什么操作;而且,与只能在 WHERE 子句中部分模拟 RLS 策略的视图不同,RLS 策略还可以用于限制用户可以对列使用哪些值。要一起使用它们,必须将它们加入请求中。

\n

将表格一分为二时非常好。但有时拆分几乎是“每列一张表”,例如每个角色一列的权限管理表。这很糟糕,因为它“原子化”了数据,而不是将其保持为适当的正常形式,这意味着管理员甚至无法以舒适的方式访问数据。这可以通过再次组合拆分表并提供对这些底层表的写入访问的视图来解决。但仍然有很多桌子需要处理。它很丑”。

\n

  • 绝对令人难以置信的答案。使用触发器(选项 2),但我这样做是为了使触发器函数仅在该表的 RLS 处于活动状态时执行,例如“WHEN (row_security_active('table_name')) EXECUTE FUNCTION function_name();” (2认同)