Supabase配置角色鉴权策略报无限递归的解决方法
问题根因
你遇到的无限递归报错是RLS(行级安全)的典型触发场景:
你写的策略中使用子查询查询users表判断当前用户是否为admin,而users表本身开启了RLS,执行这个子查询时会再次触发RLS规则校验,又要执行一遍同一个子查询,形成无限调用循环最终报错。
核心禁忌:不要在某张表的RLS策略中,直接写子查询查询这张开启了RLS的表本身,必然触发递归。
可行解决方案
方案1:基于JWT自定义声明判断(性能最优,最推荐)
Supabase登录后返回的JWT中会携带auth.users表的raw_app_meta_data字段内容,你可以直接把用户角色存在这个字段里,查询时直接读JWT值,完全不需要查public.users表,从根源避免递归:
- 创建/更新用户时,将角色写入
auth.users.raw_app_meta_data,例如给管理员用户添加{"role": "admin"}元数据 - 给
public.users表配置SELECT类型的RLS策略,表达式如下:
(auth.jwt() ->> 'role' = 'admin') AND (role = 'agent')
这个策略的逻辑是:当前登录用户JWT中携带的角色为admin时,仅允许查询role字段值为agent的行,完全没有表查询操作,性能最高也不会触发递归。
方案2:使用security definer函数封装角色查询(适配角色存在public.users表的场景)
如果你已经把角色存在public.users表中、不想调整存储位置,可以创建一个带security definer属性的函数获取当前用户角色,这类函数以函数创建者(默认为postgres超级管理员)的权限执行,会绕过RLS校验,不会触发递归:
- 执行SQL创建角色查询函数:
create or replace function public.get_current_user_role() returns text language sql stable security definer set search_path = public as $$ select role from public.users where email = auth.email() limit 1; $$;
- 给已登录的普通用户开放函数执行权限:
grant execute on function public.get_current_user_role() to authenticated;
- 配置SELECT类型的RLS策略,表达式如下:
(public.get_current_user_role() = 'admin') AND (role = 'agent')
额外注意事项
- 不要在
public.users表中存储明文密码,Supabase Auth模块已经在auth.users表中存储了加盐加密后的用户密码,public表存明文会有严重的数据泄露风险 - 要确保
public.users.email字段的值和auth.users.email完全一一对应,否则会出现角色判断错误的问题
内容的提问来源于stack exchange,提问作者Yash
相关产品推荐
相关产品推荐

