数据库

自定义声明与基于角色的访问控制(RBAC)


自定义声明(Custom Claims)是附加到用户的特殊属性,可用于控制应用程序不同部分的访问权限。例如:

1
2
3
4
5
6
7
8
9
{ "user_role": "admin", "plan": "TRIAL", "user_level": 100, "group_name": "Super Guild!", "joined_on": "2022-05-20T14:28:18.217Z", "group_manager": false, "items": ["toothpick", "string", "ring"]}

要实现基于自定义声明的角色访问控制(RBAC),请使用自定义访问令牌认证钩子。该钩子在令牌颁发前执行,可用于向用户的JWT添加额外声明。

本指南使用Slack克隆示例来演示如何添加user_role声明,并将其用于行级安全(RLS)策略中。

创建用户角色和权限表

在本示例中,您将实现两种具有特定权限的用户角色:

  • moderator(版主):可以删除所有消息但不能删除频道
  • admin(管理员):可以删除所有消息和频道
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 自定义类型create type public.app_permission as enum ('channels.delete', 'messages.delete');create type public.app_role as enum ('admin', 'moderator');-- 用户角色表create table public.user_roles ( id bigint generated by default as identity primary key, user_id uuid references auth.users on delete cascade not null, role app_role not null, unique (user_id, role));comment on table public.user_roles is '每个用户的应用程序角色';-- 角色权限表create table public.role_permissions ( id bigint generated by default as identity primary key, role app_role not null, permission app_permission not null, unique (role, permission));comment on table public.role_permissions is '每个角色的应用程序权限';

现在您可以通过SQL管理角色和权限。例如,要添加上述提到的角色和权限,请执行:

1
2
3
4
5
insert into public.role_permissions (role, permission)values ('admin', 'channels.delete'), ('admin', 'messages.delete'), ('moderator', 'messages.delete');

创建认证钩子以应用用户角色

自定义访问令牌认证钩子在令牌颁发前运行,可用于编辑JWT。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
-- 创建认证钩子函数create or replace function public.custom_access_token_hook(event jsonb)returns jsonblanguage plpgsqlstableas $$ declare claims jsonb; user_role public.app_role; begin -- 从user_roles表中获取用户角色 select role into user_role from public.user_roles where user_id = (event->>'user_id')::uuid; claims := event->'claims'; if user_role is not null then -- 设置声明 claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role)); else claims := jsonb_set(claims, '{user_role}', 'null'); end if; -- 更新原始事件中的'claims'对象 event := jsonb_set(event, '{claims}', claims); -- 返回修改后或原始的事件 return event; end;$$;grant usage on schema public to supabase_auth_admin;grant execute on function public.custom_access_token_hook to supabase_auth_admin;revoke execute on function public.custom_access_token_hook from authenticated, anon, public;grant all on table public.user_rolesto supabase_auth_admin;revoke all on table public.user_roles from authenticated, anon, public;create policy "允许认证管理员读取用户角色" ON public.user_rolesas permissive for selectto supabase_auth_adminusing (true)

启用钩子

在控制面板中,导航至 认证 > 钩子(测试版) 并从下拉菜单中选择相应的Postgres函数。

在本地开发时,请遵循本地开发指南。

在RLS策略中访问自定义声明

要在行级安全(RLS)策略中实现基于角色的访问控制(RBAC),可以创建一个authorize方法,该方法从用户的JWT中读取角色并检查角色权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
create or replace function public.authorize( requested_permission app_permission)returns boolean as $$declare bind_permissions int; user_role public.app_role;begin -- 获取用户角色并存储以减少调用次数 select (auth.jwt() ->> 'user_role')::public.app_role into user_role; select count(*) into bind_permissions from public.role_permissions where role_permissions.permission = requested_permission and role_permissions.role = user_role; return bind_permissions > 0;end;$$ language plpgsql stable security definer set search_path = '';

然后您可以在RLS策略中使用authorize方法。例如,要启用所需的删除权限,可以添加以下策略:

1
2
create policy "允许授权删除访问" on public.channels for delete to authenticated using ( (SELECT authorize('channels.delete')) );create policy "允许授权删除访问" on public.messages for delete to authenticated using ( (SELECT authorize('messages.delete')) );

在应用中访问自定义声明

Auth Hook 只会修改访问令牌 JWT 而不会修改认证响应。因此,要在您的应用中访问自定义声明(例如浏览器客户端或服务器端中间件),您需要解码认证会话中的 access_token JWT。

在 JavaScript 客户端应用中,您可以使用 jwt-decode

1
2
3
4
5
6
7
8
import { jwtDecode } from 'jwt-decode'const { subscription: authListener } = supabase.auth.onAuthStateChange(async (event, session) => { if (session) { const jwt = jwtDecode(session.access_token) const userRole = jwt.user_role }})

对于服务器端逻辑,您可以使用诸如 express-jwtkoa-jwtPyJWTdart_jsonwebtokenMicrosoft.AspNetCore.Authentication.JwtBearer 等包。

总结

您现在拥有了一套完善的系统来管理数据库中的用户角色和权限,这些权限会自动同步到 Supabase Auth。

更多资源