数据库

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


自定义声明是附加到用户的特殊属性,可用于控制应用程序的某些部分的访问权限。例如

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

要使用 自定义声明 实现基于角色的访问控制 (RBAC),请使用 自定义访问令牌身份验证钩子。此钩子在颁发令牌之前运行。您可以使用它来向用户的 JWT 添加额外的声明。

本指南使用 Slack 克隆示例 来演示如何添加 user_role 声明并在您的 行级安全 (RLS) 策略 中使用它。

创建表以跟踪用户角色和权限#

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

  • moderator:版主可以删除所有消息,但不能删除频道。
  • admin:管理员可以删除所有消息和频道。
1
-- Custom types
2
create type public.app_permission as enum ('channels.delete', 'messages.delete');
3
create type public.app_role as enum ('admin', 'moderator');
4
5
-- USER ROLES
6
create table public.user_roles (
7
id bigint generated by default as identity primary key,
8
user_id uuid references auth.users on delete cascade not null,
9
role app_role not null,
10
unique (user_id, role)
11
);
12
comment on table public.user_roles is 'Application roles for each user.';
13
14
-- ROLE PERMISSIONS
15
create table public.role_permissions (
16
id bigint generated by default as identity primary key,
17
role app_role not null,
18
permission app_permission not null,
19
unique (role, permission)
20
);
21
comment on table public.role_permissions is 'Application permissions for each role.';

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

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

创建身份验证钩子以应用用户角色#

在颁发令牌之前运行 自定义访问令牌身份验证钩子。您可以使用它来编辑 JWT。

1
-- Create the auth hook function
2
create or replace function public.custom_access_token_hook(event jsonb)
3
returns jsonb
4
language plpgsql
5
stable
6
as $$
7
declare
8
claims jsonb;
9
user_role public.app_role;
10
begin
11
-- Fetch the user role in the user_roles table
12
select role into user_role from public.user_roles where user_id = (event->>'user_id')::uuid;
13
14
claims := event->'claims';
15
16
if user_role is not null then
17
-- Set the claim
18
claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
19
else
20
claims := jsonb_set(claims, '{user_role}', 'null');
21
end if;
22
23
-- Update the 'claims' object in the original event
24
event := jsonb_set(event, '{claims}', claims);
25
26
-- Return the modified or original event
27
return event;
28
end;
29
$$;
30
31
grant usage on schema public to supabase_auth_admin;
32
33
grant execute
34
on function public.custom_access_token_hook
35
to supabase_auth_admin;
36
37
revoke execute
38
on function public.custom_access_token_hook
39
from authenticated, anon, public;
40
41
grant all
42
on table public.user_roles
43
to supabase_auth_admin;
44
45
revoke all
46
on table public.user_roles
47
from authenticated, anon, public;
48
49
create policy "Allow auth admin to read user roles" ON public.user_roles
50
as permissive for select
51
to supabase_auth_admin
52
using (true);

启用钩子#

在仪表板中,导航到 身份验证 > 钩子 (Beta) 并从下拉菜单中选择适当的 Postgres 函数。

在本地开发时,请遵循 本地开发 说明。

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

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

1
create or replace function public.authorize(
2
requested_permission app_permission
3
)
4
returns boolean as $$
5
declare
6
bind_permissions int;
7
user_role public.app_role;
8
begin
9
-- Fetch user role once and store it to reduce number of calls
10
select (auth.jwt() ->> 'user_role')::public.app_role into user_role;
11
12
select count(*)
13
into bind_permissions
14
from public.role_permissions
15
where role_permissions.permission = requested_permission
16
and role_permissions.role = user_role;
17
18
return bind_permissions > 0;
19
end;
20
$$ language plpgsql stable security definer set search_path = '';

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

1
create policy "Allow authorized delete access" on public.channels for delete to authenticated using ( (SELECT authorize('channels.delete')) );
2
create policy "Allow authorized delete access" on public.messages for delete to authenticated using ( (SELECT authorize('messages.delete')) );

在您的应用程序中访问自定义声明#

身份验证钩子只会修改访问令牌 JWT,而不会修改身份验证响应。因此,要在您的应用程序中访问自定义声明,例如您的浏览器客户端或服务器端中间件,您需要在身份验证会话中解码 access_token JWT。

在 JavaScript 客户端应用程序中,例如可以使用 jwt-decode

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

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

结论#

现在,您已经建立了一个强大的系统来管理数据库中的用户角色和权限,该系统会自动传播到 Supabase 身份验证。

更多资源#