English TL;DR: A “read-only” Supabase setup still needs proper RLS and table boundaries. Use publishable keys only with strict policies; keep write paths server-side.
独立开发做数据站/工具站时,Supabase 很顺手:
- Postgres + 管理台
- REST/Realtime/SDK
- 部署成本低
但也很容易踩坑:
- “反正是只读” → 直接开放表
- publishable key 泄露后,被人把你的配额刷爆
- RLS 写错,一不小心把敏感字段放出去
这篇讲清楚一个核心问题:
你到底应该开放什么?不应该开放什么?
1) publishable key 是什么:它不是“秘密”,它是“能力门票”
Supabase 的 publishable key(匿名 key)本质是:
- 客户端可以带着它访问你的 Supabase
- 访问权限由 RLS(Row Level Security) 决定
所以 publishable key 泄露并不稀奇(它本来就会在前端里)。 真正决定安全性的,是:
- RLS 是否严格
- 表结构是否合理
- 是否把写入/敏感查询留在服务端
2) 只读数据站的推荐边界(最小可行)
把系统拆成三层:
-
采集/计算层(你的服务器)
- 抓数据
- 清洗
- 写入数据库
-
数据服务层(Supabase Postgres)
- 存储结果
- 通过 View / Materialized View 输出“可公开消费”的表
-
展示层(Next.js / Vercel)
- 仅做查询 + 展示
最关键的一条:
不要让前端直接查询“原始表”。
原始表通常包含:
- 内部字段
- 供应商 id
- 采集状态
- 失败信息
这些都不适合公开。
3) 推荐做法:用 View 暴露“public shape”
例如:
raw_funding_rates(内部表)public_funding_rates(view)
public_* 的字段应当:
- 只包含你愿意公开的列
- 不包含任何内部 id/token/provider 字段
- 尽量规范化:单位、精度、时间戳
然后 RLS 只对 public_* 生效(或者你直接只允许访问 view 相关 schema)。
4) RLS(Row Level Security)怎么写才不会翻车
如果你的数据是完全公开的(比如行情/费率),你依然建议:
- 开启 RLS
- 明确写出允许 select 的 policy
例子(思路示意):
- 允许所有人
select - 禁止所有人
insert/update/delete
这样就算有人拿到 publishable key,也只能读你允许的那部分。
5) 三个最常见坑
坑 1:忘了开启 RLS
你以为写了 policy,但如果没开启 RLS,policy 根本不生效。
坑 2:把 write path 放在客户端
比如:
- 用户提交表单直接写库
- 客户端直接调用
insert
这会让你非常难做风控(限流、反刷、审计)。
正确做法:
- 写入走 Next.js Route Handler(服务端)
- 或者走你自己的采集服务
坑 3:把敏感表 join 到 public view
只要 public view 最终能被 select,它 join 出来的字段就可能被推断/泄露。
6) 你应该怎么“验证只读真的只读”
最小验证:
- 用 publishable key 在浏览器里尝试 select(成功)
- 尝试 insert/update/delete(必须失败)
- 尝试访问原始表(必须失败)
如果你愿意再加一层:
- 对外查询统一走 RPC(SQL function)
- 限制参数范围
这样更好控,但会牺牲一些开发效率。
7) 什么时候该用 service role key?
结论非常简单:
- 只要是写入/敏感读取 → service role key 只能在服务端
- 永远不要把 service role key 放到前端
如果你正在做类似 PerpHQ 这种数据站:
- 前端只读 + publishable key + 严格 RLS
- 写入/计算放到采集服务
- public view 输出你想让外部看到的数据形状
这是长期最省心、也最稳的一套。