
本文详解如何通过合理设计数据模型与 rls 策略,防止前端绕过验证恶意刷鼓掌次数,确保计数行为真实、可审计、不可伪造。
本文详解如何通过合理设计数据模型与 rls 策略,防止前端绕过验证恶意刷鼓掌次数,确保计数行为真实、可审计、不可伪造。
在基于 Supabase 构建的互动应用(如线上剧场)中,“虚拟鼓掌”看似简单,实则暗藏安全风险:若仅依赖客户端发起 UPDATE 请求并配合宽松的 RLS(如 auth.role() = 'authenticated'),攻击者可通过浏览器控制台或 Postman 复用有效 JWT 和 API Key,反复提交请求,轻松刷高计数——这本质上是将业务逻辑(“一人一拍”“限频防刷”)错误地交由不可信的前端执行。
根本解法:拒绝直接更新计数字段,转为记录原子化操作事件
应摒弃 UPDATE posts SET clap_count = clap_count + 1 这类易被滥用的模式,改为创建专用的 claps 表,以不可变日志(immutable log) 方式持久化每一次合法鼓掌动作。每条记录明确绑定 user_id、post_id(或目标资源 ID)和时间戳,既保证可追溯性,又为后续风控策略(如去重、限频、权限校验)提供数据基础。
以下为推荐的数据结构与 RLS 实现:
-- 创建 claps 表(使用复合主键天然防重复)
create table if not exists public.claps (
post_id uuid references public.posts on delete cascade not null,
user_id uuid references public.users on delete cascade not null,
created_at timestamp with time zone default timezone('utc' :: text, now()) not null,
primary key (post_id, user_id) -- 同一用户对同一帖子仅允许一次鼓掌
);
-- 启用行级安全
alter table public.claps enable row level security;
-- 允许所有人查询(用于统计总鼓掌数)
create policy "Anyone can read claps"
on claps for select
using (true);
-- 仅允许用户插入自己发起的鼓掌(关键防护!)
create policy "Users can insert only their own clap"
on claps for insert
with check (auth.uid() = user_id);
✅ 该方案的核心优势:
- 服务端强制校验身份:auth.uid() = user_id 在数据库层执行,无法被前端篡改;
- 天然幂等性:复合主键 (post_id, user_id) 确保同一用户对同一内容无法重复鼓掌;
- 零信任设计:不依赖前端传入的 user_id,而是由 Supabase 自动注入当前认证用户的 UUID;
- 扩展性强:后续如需支持“每日最多 5 次”“间隔至少 30 秒”,只需在插入前增加自定义 RLS 策略或数据库函数校验。
⚠️ 重要注意事项:
- 前端必须调用 supabase.from('claps').insert({ post_id: 'xxx' })(不传 user_id),由 Supabase 自动填充;若手动传入 user_id,需在 RLS 中严格禁止(上述 with check 已覆盖);
- 避免将 clap_count 字段冗余存储在 posts 表中——应始终通过 SELECT COUNT(*) FROM claps WHERE post_id = ? 实时聚合,或使用数据库视图/物化视图优化性能;
- 若需更高性能,可结合 Supabase 的 Realtime 或数据库触发器,在 claps 表变更时异步更新 posts.clap_count,但计数权威源必须是 claps 表本身;
- JWT 过期时间(如设为 1 秒)不是解决方案,而是反模式:它破坏用户体验且不解决本质问题——只要 token 有效,攻击仍可发生。
总结而言,安全的鼓掌功能不在于“让请求更快失效”,而在于“让非法请求从数据库层就被拒绝”。通过事件溯源建模 + 精准 RLS 策略,你构建的不仅是一个计数器,更是一套可审计、可扩展、符合零信任原则的用户交互基础设施。










