SuperAPI AI 工具集

supabase-security-basics

安全与审计

查看项目

更新于 10-16 09:10

技能介绍

用途

Supabase 通过 PostgREST 直接把 Postgres 数据库暴露到互联网。任何没有启用行级安全(RLS)的表,只要别人拿到项目 URL 和 anon 密钥(这两个都是公开的),就能完整读写。本技能讲解 Supabase 安全的三根支柱:密钥分离(anon 与 service_role)、RLS 策略执行、API 面硬化。适用场景是使用 Supabase 构建应用的开发者,需要在建表、接入客户端与上线前落实安全基线。

主要能力

一、区分两个 API 密钥

Supabase 每个项目发放两个密钥,混淆二者是最常见的安全错误。

  • anon 密钥:环境变量 SUPABASE_ANON_KEY,可暴露给客户端(浏览器安全),遵守所有 RLS 策略。
  • service_role 密钥:环境变量 SUPABASE_SERVICE_ROLE_KEY,绝不可暴露,绕过全部 RLS。

anon 密钥是一个 JWT,PostgREST 用它判断哪些 RLS 策略适用,放入客户端 bundle 是安全的,它只能访问 RLS 策略显式允许的数据。service_role 密钥绕过每一条 RLS 策略,只应存在于服务端代码(API 路由、Edge Functions、定时任务、迁移脚本)。

密钥轮换:在控制台 Settings > API 重新生成密钥。轮换后需更新所有环境变量并重新部署全部服务。旧密钥立即失效,没有宽限期。

二、为每张表启用 RLS

没有 RLS 时,public schema 中的任何表都会对持有 anon 密钥的人通过 REST API 完全开放。RLS 不是可选项,它是主要访问控制层。可使用查询审计缺失 RLS 的表,并对每张 public 表执行 ALTER TABLE ... ENABLE ROW LEVEL SECURITY。

关键点:启用 RLS 却没有任何策略,会阻断通过 API 的全部访问,必须为每张表的每个操作(SELECT、INSERT、UPDATE、DELETE)至少添加一条策略。

提供的策略模式包括:

  • 用户读写自己的行:基于 auth.uid() = user_id。
  • 公开读、已认证写。
  • 基于自定义 JWT 声明(app_metadata)的角色访问,如管理员全访问。
  • 按组织范围的访问,通过成员表关联校验。

USING 与 WITH CHECK 的区别:USING 过滤用户能看到哪些既有行(SELECT、UPDATE、DELETE);WITH CHECK 校验新增或修改后的行数据(INSERT、UPDATE)。对 UPDATE 两者都要:USING 控制可定位哪些行,WITH CHECK 控制新值可以是什么。

三、硬化 API 面

  • JWT 校验:Supabase 在服务端自动校验 JWT,RLS 策略中的 auth.uid() 从已校验的 JWT 中提取用户 ID,无需在策略中手动校验令牌。
  • SQL 注入防护:JS SDK 内部使用参数化查询。绝不要用用户输入拼接原始 SQL 字符串,始终使用 SDK 查询构建器;只在 supabase.rpc() 中使用参数化函数,绝不用模板字符串。
  • 网络限制:在控制台 Settings > Database > Network Restrictions 将直连数据库限制在已知 IP 范围内。这不影响走 PostgREST 的 REST API,但保护直连 Postgres 的连接。
  • CORS 配置:在 Settings > API > CORS 按项目配置允许的来源。默认允许所有来源(*),生产环境应限制到自己的域名。
  • 禁用未使用的鉴权提供方:在 Authentication > Providers 中关闭不用的提供方(邮箱、手机、Google、GitHub 等),减少攻击面。
  • SSL 强制:在 Settings > Database > SSL Configuration 为所有直连数据库的连接强制 SSL。
  • 语句超时:防止长查询耗尽数据库资源,可对 authenticated 与 anon 角色设置 statement_timeout。
  • 撤销默认 schema 授权(仅核实):Supabase 默认已处理,可用查询核实 anon 与 authenticated 角色只应通过 RLS 策略访问数据。

使用方式

按资料给出的步骤执行:先理解两个 API 密钥并在代码中正确区分;再审计并为每张表启用 RLS、补齐各操作策略;最后硬化 API 面(JWT、SQL 注入、网络限制、CORS、SSL、语句超时、禁用未用鉴权提供方)。资料提供了一个最小安全表设置示例:建表时 user_id 默认 auth.uid(),立即启用 RLS,并添加一条 FOR ALL 策略同时包含 USING 和 WITH CHECK。客户端使用 anon 密钥创建客户端后,查询会自动被 RLS 策略按 auth.uid() 过滤。

完成后的状态

  • anon 密钥仅用于客户端代码,service_role 密钥仅限服务端。
  • 每张 public 表都启用 RLS,且每个操作有明确策略。
  • 为基于角色的访问配置了自定义 JWT 声明。
  • 网络限制、CORS、SSL、语句超时均已硬化。
  • 未使用的鉴权提供方已禁用。

安全审计清单(资料列出)

  • 所有 public 表启用 RLS(SELECT rowsecurity FROM pg_tables WHERE schemaname='public')。
  • 每张表在每个需要的操作上至少有一条 RLS 策略。
  • service_role 密钥不在任何客户端或 NEXT_PUBLIC_* 环境变量中。
  • .env 文件已在 .gitignore 中。
  • 启用邮箱确认(Dashboard > Authentication > Settings)。
  • OAuth 重定向 URL 限制到自己的域名。
  • 禁用未使用的鉴权提供方。
  • 启用 SSL 强制(Dashboard > Database > SSL)。
  • 修改默认数据库密码。
  • 为直连数据库连接配置网络限制。
  • 为 authenticated 和 anon 角色设置 statement_timeout。
  • 为敏感用户操作启用 MFA。
  • 生产环境启用时间点恢复(PITR)。
  • 怀疑密钥泄露后立即轮换 API 密钥。

错误处理

  • 42501: new row violates row-level security policy:缺少 RLS 策略或 WITH CHECK 条件不通过;补充或修复该操作的策略,核实 auth.uid() 与行的用户列匹配。
  • 查询返回空数据且无错误:RLS 的 USING 子句过滤掉了所有行;核实策略中的 auth.uid() 与已认证用户匹配,检查 JWT 声明。
  • PGRST301: JWSError:JWT 无效或过期;重新认证用户,核实 SUPABASE_ANON_KEY 与项目一致。
  • PGRST302: anonymous access disabled:客户端初始化未提供 anon 密钥;向 createClient() 传入 anon 密钥,检查环境变量是否设置。
  • permission denied for table X:RLS 已启用但没有匹配策略;为该特定操作(SELECT/INSERT/UPDATE/DELETE)创建策略。
  • Could not find the function auth.uid():在 PostgREST 上下文之外运行 SQL;auth.uid() 只在 PostgREST 求值的 RLS 策略中有效,迁移脚本中使用显式用户 ID。

前置条件

  • 已创建 Supabase 项目(本地或托管)并能访问控制台。
  • 已安装 @supabase/supabase-js(npm install @supabase/supabase-js)。
  • 已配置 SUPABASE_URL 和 SUPABASE_ANON_KEY 环境变量。
  • 具备 SQL 与 Postgres 基础。

限制

资料未提供关于收费、价格、平台差异或实际效果的说明。RLS 策略写法、密钥管理与审计清单均来自资料,未额外扩展。