REST API

保护您的API


数据API设计用于与Postgres行级安全(RLS)协同工作。如果您使用Supabase认证,可以根据登录用户限制数据访问。

要控制数据访问权限,您可以使用策略

启用行级安全

public模式中创建的任何表都可以通过Supabase数据API访问。

要限制访问,请为public模式中的所有表、视图和函数启用行级安全(RLS)。然后您可以编写RLS策略,根据用户的认证令牌授予特定数据库行或函数的访问权限。

通过Supabase仪表板创建的任何表默认都会启用RLS。如果您是通过SQL编辑器或其他方式创建的表,请按以下方式启用RLS:

  1. 进入仪表板的认证 > 策略页面
  2. 选择启用RLS来开启行级安全

启用RLS后,您可以创建策略来允许或禁止用户访问和更新数据。我们在授权文档中提供了创建行级安全策略的详细指南。

禁用API或限制为自定义模式

如果您不使用数据API,或者不想暴露public模式,您可以完全禁用它,或者将自动暴露的模式更改为您选择的模式。具体操作请参见**强化数据API安全**指南。

为每个请求强制执行额外规则

仅使用行级安全策略(Row Level Security)有时不足以充分保护API。以下是需要额外保护的常见场景:

  • 实施基于IP或用户的速率限制
  • 在允许进一步访问前检查自定义或额外的API密钥
  • 超过配额或需要付款时拒绝请求
  • 禁止直接访问public模式中的特定表、视图或函数

您可以通过创建一个Postgres函数在应用中实现这些场景,该函数会读取请求信息并执行额外检查,例如统计接收到的请求数量或在响应前验证API密钥是否已在数据库中注册。

定义如下函数:

1
2
3
4
5
6
7
8
9
create function public.check_request() returns void language plpgsql security definer as $$begin -- 在此处添加您的逻辑end;$$;

并通过以下方式注册该函数使其在每个数据API请求上运行:

1
2
alter role authenticator set pgrst.db_pre_request = 'public.check_request';

这将配置public.check_request函数在每个数据API请求上运行。要使更改生效,您需要执行:

1
notify pgrst, 'reload config';

在函数内部,您可以对请求头或JWT执行任何额外检查,并通过抛出异常来阻止请求完成。例如,以下异常会返回HTTP 402 Payment Required响应,包含hint和额外的X-Powered-By头信息:

1
2
3
4
5
6
7
8
9
10
raise sqlstate 'PGRST' using message = json_build_object( 'code', '123', 'message', 'Payment Required', 'details', 'Quota exceeded', 'hint', 'Upgrade your plan')::text, detail = json_build_object( 'status', 402, 'headers', json_build_object( 'X-Powered-By', 'Nerd Rage'))::text;

当在public.check_request函数中抛出时,生成的HTTP响应如下:

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 402 Payment RequiredContent-Type: application/json; charset=utf-8X-Powered-By: Nerd Rage{ "message": "Payment Required", "details": "Quota exceeded", "hint": "Upgrade your plan", "code": "123"}

使用PostgreSQL JSON操作函数可以从异常构建丰富且动态的响应。

如果使用自定义HTTP状态码如419,可以在异常的detail子句中提供status_text键来描述HTTP状态。

如果使用PostgREST 11或更低版本(查看您的PostgREST版本),则需要使用功能较弱的不同语法

访问请求信息

与RLS策略类似,您可以通过Postgres的current_setting()函数访问请求相关信息。以下是一些使用示例:

1
2
3
4
5
6
7
8
-- 获取请求中的所有头部信息SELECT current_setting('request.headers', true)::json;-- 获取单个头部,可以使用JSON箭头操作符SELECT current_setting('request.headers', true)::json->>'user-agent';-- 访问CookiesSELECT current_setting('request.cookies', true)::json;
current_setting()示例描述
request.methodGET, HEAD, POST, PUT, PATCH, DELETE请求方法
request.pathtable表路径
request.pathview视图路径
request.pathrpc/function函数路径
request.headers{ "User-Agent": "...", ... }请求头部的JSON对象
request.cookies{ "cookieA": "...", "cookieB": "..." }请求Cookies的JSON对象
request.jwt{ "sub": "a7194ea3-...", ... }JWT负载的JSON对象

要获取客户端IP地址,可以在request.headers设置中查找X-Forwarded-For头部。例如:

1
2
3
SELECT split_part( current_setting('request.headers', true)::json->>'x-forwarded-for', ',', 1); -- 获取第一个逗号(,)前的客户端IP

了解更多关于PostgREST的预请求函数

示例

您只能对POSTPUTPATCHDELETE请求进行速率限制。这是因为GETHEAD请求以只读模式运行,会由只读副本提供服务,而这些副本不支持写入数据库。

方案概述:

  • 每次对数据库执行修改操作时,都会在private.rate_limits表中添加一行记录,包含IP地址和操作时间戳。
  • 如果同一IP地址在最近5分钟内超过100次请求,则返回HTTP 420代码拒绝请求。

创建表:

1
2
3
4
5
6
7
create table private.rate_limits ( ip inet, request_at timestamp);-- 添加索引以加速查询create index rate_limits_ip_request_at_idx on private.rate_limits (ip, request_at desc);

使用private模式是因为它无法通过API访问!

创建public.check_request函数:

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
create function public.check_request() returns void language plpgsql security definer as $$declare req_method text := current_setting('request.method', true); req_ip inet := split_part( current_setting('request.headers', true)::json->>'x-forwarded-for', ',', 1)::inet; count_in_five_mins integer;begin if req_method = 'GET' or req_method = 'HEAD' or req_method is null then -- GET和HEAD请求无法进行速率限制 return; end if; select count(*) into count_in_five_mins from private.rate_limits where ip = req_ip and request_at between now() - interval '5 minutes' and now(); if count_in_five_mins > 100 then raise sqlstate 'PGRST' using message = json_build_object( 'message', '超过速率限制,请稍后再试')::text, detail = json_build_object( 'status', 420, 'status_text', '请保持冷静')::text; end if; insert into private.rate_limits (ip, request_at) values (req_ip, now());end; $$;

最后配置public.check_request()函数在每个Data API请求上运行:

1
2
3
4
alter role authenticator set pgrst.db_pre_request = 'public.check_request';notify pgrst, 'reload config';

要清理private.rate_limits表中的旧条目,可以设置pg_cron任务定期清理。