Supabase 独立开发<三>:Database

6 阅读6分钟

上篇文章我们已经了解了 Supabase Auth,用户管理和注册登录已经没有问题了。这篇文章我们集中在 Database 这一层,这也是 Supabase 提高生产力的核心。

数据库作为后端服务

在正常的软件开发中,前端调用后端,后端调用数据库,后端需要开发者进行开发。在 Supabase 中,你不再需要开发后端了,Supabase 为你生成好了常用的后端接口,被称为 Data API,你可以直接通过 supabase.com/dashboard/p…/api 来查看接口使用文档,包括增删改查过滤分页等等。在接口形式上,支持两种形式:Restful API 和 GraphQL API,另外还为这两种方式提供了主流语言的 SDK。我的建议是,通过 SDK 使用 Restful API 的形式来交互,既简单又对 VibeCoding 友好。在能力上,你可以操作两种类型的资源,数据库表 和 数据库函数。

数据库表和 RLS

现在我们可以通过前端直接操作数据库的CRUD了,但是此时这个数据库表是对所有用户完全开放的。为了做权限上的限制,需要开启 RLS 功能,这个功能用于可以直接在页面上开启,或者通过 pgsql 语句开启。那么 RLS 是什么呢?RLS 的名称叫做行级安全性,是 Postgres 本身的一项能力,它用于指定在什么样的用户可以访问这个表的哪些行。那么如何配置呢?如果你直接从页面上配置,你会非常的懵逼,我在这一点上踩了很久的坑才明白。

Table RLS 的语法

注意 1:RLS 是 Postgres 本身的功能,所以不要被 Supabase 迷惑。你总是可以通过 pgsql 语句来创建策略。

我们先来看它的语法。

CREATE POLICY update_own_posts ON posts
AS PERMISSIVE
TO authenticated
FOR UPDATE
USING (author_id = auth.uid())           -- 你只能找回你自己的旧帖子
WITH CHECK (author_id = auth.uid());     -- 你修改后的数据,author_id 必须还是你

ON:表示这个 RLS 策略应用在那个表上。 AS:PERMISSIVE 表示这个策略是允许型,除此之外还有 RESTRICTIVE,表示这个策略是限制型的,这个过于复杂,使用场景比较少,不讲,总是使用 PERMISSIVE 就可以了。 TO:表示这个策略应用在哪个角色上,这个非常关键,后面详细讲。 FOR:表示在哪种操作执行时使用这个策略,有 UPDATE、INSERT、DELETE、SELECT 四种。 USING:表示在执行操作时,需要满足什么条件,或者说,只有满足了这里的条件,才是你能操作的行。 WITH CHECK:表示在执行操作后,需要满足什么条件,或者说,只有你变更后满足这个条件,才能真正的保存。

对于 USING:很明显,对于 INSERT 的 操作,是不支持的,因为新行还没有被插入。 对于 WITH CHECK:也很明显,只支持 INSERT 和 UPDATE,因为这两个操作才会进行变更。

使用 RLS 授权的技巧

使用 RLS 授权其实就是在填写这个语句中的字段。

AS:总是填写 PERMISSIVE,可以覆盖90%的场景,除非你知道自己 RESTRICTIVE 是什么含义。

TO:你授权的角色,你从页面上创建时,可以看到很多 role,但是你只需要关注四个 role。

  • anon: 表示还没登录的用户,对应着 Supabase Auth 章节中提到的 Anon Role Key
  • authenticated: 表示已经登录的用户,对应着 Supabase Auth 章节中在登录后获取到的 Authenticated JWT。
  • service_role: 用于你的其它非公开服务通过 SDK 连接 Supabase,对应着 Supabase Auth 章节中在登录后获取到的 Service Role Key,不受到 RLS 的限制,拥有完全的数据访问权限。
  • postgres: 数据库的最高管理权限,一般你直接使用这个连接并管理数据库,在 Edge Function 中,你也可以使用这个账户通过 ORM 之类的技术来连接。
  • public:并不是一个真正的角色,指所有角色,我的评价是,忘记它,别用。

如果只是这样,那么还是不足以做精细化的控制,必须借助 USING 和 WITH CHECK,这两个语句都是 bool 表达式,通过表达式你可以实现非常灵活的策略,重点是:Supabase 提供了两个函数,auth.uid() 和 auth.jwt(),这两个函数足以让你拿到足够多的信息去做权限控制。

数据库函数

在 Supabase 中,使用 Data API 除了可以直接进行数据库表的增删改查,还可以调用数据库函数(或者叫做存储过程)。但是数据库函数的权限控制官方似乎并没有一个比较好的实践指南,我自己总结了一个我个人用的最佳实践。首先要理解,数据库函数的权限存在两个维度:函数的可见性和函数的执行权限。

数据库函数的可见性。

函数的可见性指的是 supabase 客户端 可以使用的函数。函数的隔离,使用 public 和 public_internal 两个 schema。通过将内部使用的函数放在 public_internal schema 中,将 Supabase 客户端使用的函数放在 public schema 中,可以有效的控制客户端可以使用的函数,这种方式类似于许多语言中的包可见性。

在 public_internal 模式中,使用宽松的安全上下文

对于 public_internal 模式中的私有函数,因为不会被 客户端调用,相当于后端逻辑,可以给一个较大的权限。可以简单的授权给 public 角色,并且使用 SECURITY DEFINER postgres。SECURITY DEFINER 是函数执行的安全上下文,函数将会以这个角色的身份权限运行,postgres 角色可以绕过 RLS 的限制,确保开发时不会遇到奇怪的 RLS 错误。总是使用 SET search_path = public 来约束搜索路径。

在 public 模式中,检查权限

在 public 模式中,我们需要做好授权,你需要确定该函数对于 anon 还是 authenticated 角色开放。对于特定用户的权限,应该使用 auth.uid() 和 auth.jwt() 来检查权限。并且对于这些函数,总是要使用 SECURITY INVOKER 这样的调用者安全上下文,因为函数的执行身份是 anon 或者 authenticated,所以 RLS 此时是生效的。

最佳实践

  • 使用 public 和 public_internal 两个模式来隔离函数可见性
  • 在 public_internal 中,使用宽松的安全上下文来简化开发
  • 在 public 中,确保函数的角色授权和用户授权正确,并确保安全上下文为 SECURITY INVOKER