行级别安全性
当您需要更精细的授权规则时,PostgreSQL的行级安全(RLS)是很好的选择。
策略是PostgreSQL的规则引擎。它们非常强大和灵活,允许你编写复杂的SQL规则,以满足你独特的业务需求。
策略
每个数据库表可以添加一个或多个策略(policy),每当表被访问(增,删,改、查等操作)时,都会执行(校验)设置的策略,就好比为每个sql语句添加了一个where的查询条件。以下是一个策略的例子:
1create policy "Individuals can view their own todos."
2 on todos for select
3 using ( auth.uid() = user_id );
上面的策略在执行sql查询时被转化成以下语句(每当用户查询todos这张表时,只会查询包含自己ID的记录):
1select *
2from todos
3where auth.uid() = todos.user_id; -- Policy is implicitly added.
辅助函数
Supabase为您提供了一些简单的功能,您可以在策略中使用这些功能。
auth.uid()
#
返回当前前端请求的用户id值。
auth.jwt()
#
返回当前前端请求的用户jwt值。
示例
下面是一些示例,向您展示PostgreSQL的RLS的强大功能。
允许读取访问
1-- 1. Create table 2create table profiles ( 3 id uuid references auth.users, 4 avatar_url text 5); 6 7-- 2. Enable RLS 8alter table profiles 9 enable row level security; 10 11-- 3. Create Policy 12create policy "Public profiles are viewable by everyone." 13 on profiles for select using ( 14 true 15 );
- 在pulic模式(Postgre默认模式)中创建名为
profiles
的表。 - 为该表开启行级别安全。
- 创建一个策略,允许所有用户查询(select)该表的数据。
限制更新
1-- 1. Create table
2create table profiles (
3 id uuid references auth.users,
4 avatar_url text
5);
6
7-- 2. Enable RLS
8alter table profiles
9 enable row level security;
10
11-- 3. Create Policy
12create policy "Users can update their own profiles."
13 on profiles for update using (
14 auth.uid() = id
15 );
- 在pulic模式(Postgre默认模式)中创建名为
profiles
的表。 - 为该表开启行级别安全。
- 创建一个策略,仅允许当前登录用户更新(update)自己的数据。
仅限匿名或已认证的有权访问
您可以添加Postgres角色
1create policy "Public profiles are viewable by everyone." 2on profiles for select 3to authenticated, anon 4using ( 5 true 6);
连接相关的策略
策略甚至可以包括表联接。此示例显示了如何查询外部
表以构建更高级的规则。
1create table teams (
2 id serial primary key,
3 name text
4);
5
6-- 2. Create many to many join
7create table members (
8 team_id bigint references teams,
9 user_id uuid references auth.users
10);
11
12-- 3. Enable RLS
13alter table teams
14 enable row level security;
15
16-- 4. Create Policy
17create policy "Team members can update team details if they belong to the team."
18 on teams
19 for update using (
20 auth.uid() in (
21 select user_id from members
22 where team_id = id
23 )
24 );
注意:如果还为members启用了RLS,则用户还必须具有对members_1的读取(_select)权限。否则,连接的查询将不会产生任何结果。
具有安全定义功能的策略
策略还可以使用安全定义功能
。这在您希望限制对链接表的访问的多对多关系中非常有用。在上面的 teams
和members
示例之后,本示例显示了如何将安全定义功能与策略结合使用,以控制对 member
表的访问。
1-- 1. Follow example for 'Policies with joins' above
2
3-- 2. Enable RLS
4alter table members
5 enable row level security
6
7-- 3. Create security definer function
8create or replace function get_teams_for_authenticated_user()
9returns setof bigint
10language sql
11security definer
12set search_path = public
13stable
14as $$
15 select team_id
16 from members
17 where user_id = auth.uid()
18$$;
19
20-- 4. Create Policy
21create policy "Team members can update team members if they belong to the team."
22 on members
23 for all using (
24 team_id in (
25 select get_teams_for_authenticated_user()
26 )
27 );
验证电子邮件域
Postgres有一个函数right(string,n)
,它返回字符串中最右边的n个字符。 您可以使用它来匹配员工的电子邮件域。
1-- 1. Create table
2create table leaderboard (
3 id uuid references auth.users,
4 high_score bigint
5);
6
7-- 2. Enable RLS
8alter table leaderboard
9 enable row level security;
10
11-- 3. Create Policy
12create policy "Only Blizzard staff can update leaderboard"
13 on leaderboard
14 for update using (
15 right(auth.jwt() ->> 'email', 13) = '@blizzard.com'
16 );
记录的有效期
策略也可以用于实现TTL或实时功能,您可以在Instagram故事或Snapchat中看到。
在下面的示例中,只有在过去24小时内创建了stories
表的行时,这些行才可用。
策略可以用于实现资源有效期限制TTL(time to live)功能,在微信中也有类似的功能,例如朋友圈仅三天可见
以下实例实现了此功能,表stories
中只可以查询到过去24小时内的记录,超过24小时的记录将查看不到。
1-- 1. Create table
2create table if not exists stories (
3 id uuid not null primary key DEFAULT uuid_generate_v4(),
4 created_at timestamp with time zone default timezone('utc' :: text, now()) not null,
5 content text not null
6);
7
8-- 2. Enable RLS
9alter table stories
10 enable row level security;
11
12-- 3. Create Policy
13create policy "Stories are live for a day"
14 on stories
15 for select using (
16 created_at > (current_timestamp - interval '1 day')
17 );
高级策略
Supabase 的高级策略功能允许您使用 SQL 的全部功能来创建复杂的规则。
在上述例子中,我们使用了两个表,即帖子表和评论表(posts
和comments
)。并且我们创建了一个评论策略,这个策略依赖于帖子策略。这意味着在执行评论策略之前,系统会检查帖子策略是否允许进行评论操作。这种依赖关系可以帮助您构建更复杂、更灵活的数据访问控制规则。
1create table posts (
2 id serial primary key,
3 creator_id uuid not null references auth.users(id),
4 title text not null,
5 body text not null,
6 publish_date date not null default now(),
7 audience uuid[] null -- many to many table omitted for brevity
8);
9
10create table comments (
11 id serial primary key,
12 post_id int not null references posts(id) on delete cascade,
13 user_id uuid not null references auth.users(id),
14 body text not null,
15 comment_date date not null default now()
16);
17
18create policy "Creator can see their own posts"
19on posts
20for select
21using (
22 auth.uid() = posts.creator_id
23);
24
25create policy "Logged in users can see the posts if they belong to the post 'audience'."
26on posts
27for select
28using (
29 auth.uid() = any (posts.audience)
30);
31
32create policy "Users can see all comments for posts they have access to."
33on comments
34for select
35using (
36 exists (
37 select 1 from posts
38 where posts.id = comments.post_id
39 )
40);
提示
为数据库表启用Realtime功能#
实时服务器根据行级别安全(RLS)策略向授权用户广播数据库更改。 建议您对添加到发布中的表启用行级别安全性并设置行安全策略。 但是,您可以选择禁用表上的RLS,并将更改广播到所有连接的客户端。
1/** 2 * REALTIME SUBSCRIPTIONS 3 * Realtime enables listening to any table in your public schema. 4 */ 5 6begin; 7 -- remove the realtime publication 8 drop publication if exists supabase_realtime; 9 10 -- re-create the publication but don't enable it for any tables 11 create publication supabase_realtime; 12commit; 13 14-- add a table to the publication 15alter publication supabase_realtime add table products; 16 17-- add other tables to the publication 18alter publication supabase_realtime add table posts;
你并不一定要使用策略
您也可以将授权规则放在中间件中,类似于使用任何其他后端<->中间件<->前端
架构,在此架构中创建安全规则的方式。
策略是一种工具。在“serverless/Jamstack”设置的情况下,它们特别有效,因为您根本不必部署任何中间件。
然而,如果您想为应用程序使用另一种授权方法,这也可以。MemFireCloud只是“普通的Postgres”,所以如果您的应用程序 与Postgres一起工作,那么它也可以与MemFireCloud一起工作。
如果你打算使用这种方法,请确保为你的表启用RLS(行级安全性)。然后使用service_role密钥(对于我们的客户端库)或postgres角色 - 这两者都可以绕过RLS。 使用这种方法,你无需创建任何策略,仅启用RLS就足够了:"
1create table profiles ( 2 id serial primary key, 3 email text 4); 5 6alter table profiles enable row level security;
切勿在客户端上使用服务密钥
MemFireCloud提供特殊的服务密钥(service_key),它可以绕过所有RLS。 而这个服务密钥不应在浏览器中使用或向客户公开,但它们对于管理任务非常有用。
caution
如果设置了用户令牌使用服务密钥初始化客户端,不会覆盖行级安全(RLS)。如果用户使用客户端登录,MemFireCloud将遵循该用户的行级安全策略。
测试相关的策略
MemFireCloud提供了一些辅助SQL过程,允许您在数据库中直接测试数据访问策略,而无需通过前端界面并模拟不同用户登录来进行测试。这些辅助过程可以帮助您更方便地验证和调试数据库的策略设置。
1grant anon, authenticated to postgres;
2
3create or replace procedure auth.login_as_user (user_email text)
4 language plpgsql
5 as $$
6declare
7 auth_user auth.users;
8begin
9 select
10 * into auth_user
11 from
12 auth.users
13 where
14 email = user_email;
15 execute format('set request.jwt.claim.sub=%L', (auth_user).id::text);
16 execute format('set request.jwt.claim.role=%I', (auth_user).role);
17 execute format('set request.jwt.claim.email=%L', (auth_user).email);
18 execute format('set request.jwt.claims=%L', json_strip_nulls(json_build_object('app_metadata', (auth_user).raw_app_meta_data))::text);
19
20 raise notice '%', format( 'set role %I; -- logging in as %L (%L)', (auth_user).role, (auth_user).id, (auth_user).email);
21 execute format('set role %I', (auth_user).role);
22end;
23$$;
24
25create or replace procedure auth.login_as_anon ()
26 language plpgsql
27 as $$
28begin
29 set request.jwt.claim.sub='';
30 set request.jwt.claim.role='';
31 set request.jwt.claim.email='';
32 set request.jwt.claims='';
33 set role anon;
34end;
35$$;
36
37create or replace procedure auth.logout ()
38 language plpgsql
39 as $$
40begin
41 set request.jwt.claim.sub='';
42 set request.jwt.claim.role='';
43 set request.jwt.claim.email='';
44 set request.jwt.claims='';
45 set role postgres;
46end;
47$$;
要切换到指定用户(通过电子邮件),使用 call auth.login_as_user('my@email.com')
;。您也可以切换到匿名角色,使用 call auth.login_as_anon()
;。
完成后,使用 call auth.logout()
; 将自己切换回 postgres
角色。
这些过程也可以用于编写针对策略的 pgTAP 单元测试。
查看psql使用此功能的交互示例。
该示例表明,在教程示例中,public.profiles
表确实可以由postgres
角色和行的所有者进行更新,但无法通过匿名
连接进行更新。
1
2postgres=> select id, email from auth.users;
3 id | email
4--------------------------------------+-------------------
5 d4f0aa86-e6f6-41d1-bd32-391f077cf1b9 | user1@example.com
6 15d6811a-16ee-4fa2-9b18-b63085688be4 | user2@example.com
7 4e1010bb-eb37-4a4d-a05a-b0ee315c9d56 | user3@example.com
8(3 rows)
9
10postgres=> table public.profiles;
11 id | updated_at | username | full_name | avatar_url | website
12--------------------------------------+------------+----------+-----------+------------+---------
13 d4f0aa86-e6f6-41d1-bd32-391f077cf1b9 | | user1 | User 1 | |
14 15d6811a-16ee-4fa2-9b18-b63085688be4 | | user2 | User 2 | |
15 4e1010bb-eb37-4a4d-a05a-b0ee315c9d56 | | user3 | User 3 | |
16(3 rows)
17
18postgres=> call auth.login_as_anon();
19CALL
20postgres=> update public.profiles set updated_at=now();
21UPDATE 0 -- anon users cannot update any profile but see all of them
22postgres=> table public.profiles;
23 id | updated_at | username | full_name | avatar_url | website
24--------------------------------------+------------+----------+-----------+------------+---------
25 d4f0aa86-e6f6-41d1-bd32-391f077cf1b9 | | user1 | User 1 | |
26 15d6811a-16ee-4fa2-9b18-b63085688be4 | | user2 | User 2 | |
27 4e1010bb-eb37-4a4d-a05a-b0ee315c9d56 | | user3 | User 3 | |
28(3 rows)
29
30postgres=> call auth.logout();
31CALL
32postgres=> call auth.login_as_user('user1@example.com');
33NOTICE: set role authenticated; -- logging in as 'd4f0aa86-e6f6-41d1-bd32-391f077cf1b9' ('user1@example.com')
34CALL
35postgres=> update public.profiles set updated_at=now();
36UPDATE 1 -- authenticated users can update their own profile and see all of them
37postgres=> table public.profiles;
38 id | updated_at | username | full_name | avatar_url | website
39--------------------------------------+-------------------------------+----------+-----------+------------+---------
40 15d6811a-16ee-4fa2-9b18-b63085688be4 | | user1 | User 1 | |
41 4e1010bb-eb37-4a4d-a05a-b0ee315c9d56 | | user2 | User 2 | |
42 d4f0aa86-e6f6-41d1-bd32-391f077cf1b9 | 2023-02-18 21:39:16.204612+00 | user3 | User 3 | |
43(3 rows)
44
45postgres=> call auth.logout();
46CALL
47postgres=> update public.profiles set updated_at=now();
48UPDATE 3 -- the 'postgres' role can update and see all profiles
49postgres=> table public.profiles;
50 id | updated_at | username | full_name | avatar_url | website
51--------------------------------------+-------------------------------+----------+-----------+------------+---------
52 15d6811a-16ee-4fa2-9b18-b63085688be4 | 2023-02-18 21:40:08.216324+00 | user1 | User 1 | |
53 4e1010bb-eb37-4a4d-a05a-b0ee315c9d56 | 2023-02-18 21:40:08.216324+00 | user2 | User 2 | |
54 d4f0aa86-e6f6-41d1-bd32-391f077cf1b9 | 2023-02-18 21:40:08.216324+00 | user3 | User 3 | |
55(3 rows)
已经弃用的功能
我们已经弃用了一些功能,以确保RLS策略具有更好的性能和可扩展性。
auth.role()
caution
auth.role()
函数已被弃用,推荐使用 Postgres 中原生支持的 TO
字段。
1-- DEPRECATED
2create policy "Public profiles are viewable by everyone."
3on profiles for select using (
4 auth.role() = 'authenticated' or auth.role() = 'anon'
5);
6
7-- RECOMMENDED
8create policy "Public profiles are viewable by everyone."
9on profiles for select
10to authenticated, anon
11using (
12 true
13);
auth.email()
caution
auth.email()
函数已被弃用,转而使用 auth.jwt() ->> 'email'
。
返回提出请求的用户的电子邮件。