保护你的API
本指南解释了如何通过 Postgres 权限、行级安全、专用模式和请求检查来保护数据 API。
🌐 This guide explains how to secure the Data API with Postgres grants, Row Level Security, dedicated schemas, and request checks.
使用这个分成两部分的指南:
🌐 Use the guide in two parts:
- 了解数据 API 安全 解释了这些控制是如何工作的以及何时使用它们。
- 配置数据 API 安全 将应用这些控制措施的步骤进行了分组。
当你需要选择安全方法时,请先阅读第一部分。当你已经知道需要配置哪些控制时,直接跳到第二部分。
🌐 Read the first section when you need to choose a security approach. Go directly to the second section when you know which controls you need to configure.
了解数据 API 安全 #
🌐 Understand Data API security
本节为本指南后面的操作提供了背景。
🌐 This section provides the context for the procedures later in the guide.
授权和行级安全 #
🌐 Grants and RLS
数据 API 使用两层 Postgres 访问控制:
🌐 The Data API works with two layers of Postgres access control:
- 权限 决定了哪些 Postgres 角色可以通过 Data API 访问表、视图或函数。这些角色包括
anon、authenticated和service_role。 - 行级安全 (RLS) 策略 决定了这些角色可以读取或修改哪些行。
权限决定一个角色是否可以访问某个对象。RLS 控制角色可以访问哪些行。每个公开的对象都要同时使用这两种控制。
🌐 Grants control whether a role can access an object. RLS controls which rows the role can access. Use both controls for every exposed object.
要应用这些控制,请参阅 显式授予访问权限 和 启用 RLS 策略。
🌐 To apply these controls, see Grant access explicitly and Enable RLS policies.
默认权限 #
🌐 Default privileges
在现有项目中,public 中创建的表默认会对 anon、authenticated 和 service_role 授予 SELECT、INSERT、UPDATE 和 DELETE 权限。函数会获得 EXECUTE。这些授权会让新对象通过数据 API 可访问,即使你并不打算公开它们。
🌐 On existing projects, tables created in public receive SELECT, INSERT, UPDATE, and DELETE privileges for anon, authenticated, and service_role by default. Functions receive EXECUTE. These grants make new objects reachable through the Data API, even when you don't intend to expose them.
Supabase 正在将平台默认设置更改为撤销这些自动授权,这样暴露就变成了可选。请查看 Supabase GitHub 讨论中的平台默认讨论。
🌐 Supabase is changing the platform default to revoke these automatic grants so that exposure becomes opt-in. See the platform defaults discussion in the Supabase GitHub discussions.
默认权限是标准 Supabase 权限模型的一部分,并不会绕过 RLS。内部的 supabase_admin 角色会将这些权限授予 anon、authenticated 和 service_role,但它无法通过数据 API 进行认证。详见 Postgres 文档中的 pg_default_acl 和 Supabase 文档中的 supabase_admin 。
🌐 The default privileges are part of the standard Supabase permission model and don't bypass RLS. The internal supabase_admin role grants them to anon, authenticated, and service_role, but it can't authenticate through the Data API. See pg_default_acl in the Postgres documentation and supabase_admin in the Supabase documentation.
要防止对新对象自动授予权限,请参阅 撤销默认权限。
🌐 To prevent automatic grants on new objects, see Revoke default privileges.
专用 API 架构 #
🌐 Dedicated API schemas
专用模式在你的数据 API 周围增加了另一层边界。像 api 这样的模式中的对象定义了 API 的界面。内部表和辅助函数仍然保留在不公开的模式中。
🌐 A dedicated schema adds another boundary around your Data API. Objects in a schema such as api define the API surface. Internal tables and helper functions remain in schemas that aren't exposed.
你可以在任何模式中通过授权来控制访问。专用模式能让你更容易识别和审计公开的部分。有关设置步骤,请参见 使用自定义模式。
🌐 You can control access with grants in any schema. A dedicated schema makes the exposed surface easier to identify and audit. See Using Custom Schemas for setup steps.
预先检查 #
🌐 Pre-request checks
RLS 政策并不能涵盖所有 API 安全需求。可以在请求前添加检查来满足以下需求:
🌐 RLS policies don't cover every API security requirement. Add pre-request checks for requirements such as:
- 对每个 IP 或每个用户实现速率限制。
- 在允许进一步访问之前检查自定义或额外的 API 密钥。
- 在超过配额或需要付费后拒绝请求。
- 不允许直接访问公开模式中的某些表、视图或函数。
Postgres 的预请求函数会读取请求信息,并在返回响应之前进行这些检查。比如,函数可以统计请求次数或验证 API 密钥。
🌐 A Postgres pre-request function reads request information and performs these checks before serving a response. For example, the function can count requests or verify an API key.
要添加检查,请参阅 配置预请求函数。
🌐 To add a check, see Configure a pre-request function.
pgrst.db_pre_request 配置只适用于 Data API(PostgREST)。它不适用于 Realtime、Storage 或其他 Supabase 产品。
🌐 The pgrst.db_pre_request configuration only works with the Data API (PostgREST). It does not work with Realtime, Storage, or other Supabase products.
如果你正在使用 db_pre_request 调用一个函数(比如 set_information()),这个函数会在每个请求上设置上下文或执行检查,而你希望其他 Supabase 产品也有类似的行为,那么你必须直接在行级安全 (RLS) 策略中调用这个函数。
🌐 If you're using db_pre_request to call a function (like set_information()) that sets up context or performs checks on every request, and you need similar behavior for other Supabase products, you must call the function directly in your Row Level Security (RLS) policies instead.
示例:
如果你有一个 db_pre_request 函数调用 set_information(),并返回 true 来设置上下文或执行检查,并且你有一个像这样的 RLS 策略:
🌐 If you have a db_pre_request function that calls set_information() that returns true to set up context or perform checks, and you have an RLS policy like:
1create policy "Individuals can view their own todos."2on todos for select3using ( (select auth.uid()) = user_id );要在其他 Supabase 产品中实现相同的行为,你需要在 RLS 策略中直接调用该函数:
🌐 To achieve the same behavior with other Supabase products, you need to call the function directly in your RLS policy:
1create policy "Individuals can view their own todos."2on todos for select3using ( set_information() AND (select auth.uid()) = user_id );这确保了在评估 RLS 策略时,该函数会被调用,适用于所有产品,而不仅仅是数据 API 请求。
🌐 This ensures the function is called when evaluating RLS policies for all products, not only Data API requests.
性能考虑:
要注意,直接在RLS策略中调用函数可能会影响数据库性能,因为在检查策略时,每行都会评估该函数。如果性能成为问题,可以考虑优化函数或使用缓存策略。
🌐 Be aware that calling functions directly in RLS policies can impact database performance, as the function is evaluated for each row when the policy is checked. Consider optimizing your function or using caching strategies if performance becomes an issue.
请求信息 #
🌐 Request information
使用 Postgres 的 current_setting() 函数来访问请求信息:
🌐 Use the Postgres current_setting() function to access request information:
1-- Get all headers sent in the request2select current_setting('request.headers', true)::json;34-- Get one header with a JSON arrow operator5select current_setting('request.headers', true)::json->>'user-agent';67-- Get cookies8select current_setting('request.cookies', true)::json;current_setting() | 示例 | 描述 |
|---|---|---|
request.method | GET、HEAD、POST、PUT、PATCH、DELETE | 请求的方法 |
request.path | table | 表的路径 |
request.path | view | 视图的路径 |
request.path | rpc/function | 函数的路径 |
request.headers | { "User-Agent": "...", ... } | 请求头的 JSON 对象 |
request.cookies | { "cookieA": "...", "cookieB": "..." } | 请求 cookie 的 JSON 对象 |
request.jwt | { "sub": "a7194ea3-...", ... } | JWT 载荷的 JSON 对象 |
要访问客户端的 IP 地址,请在 request.headers 设置中查找 X-Forwarded-For 头信息:
🌐 To access the client's IP address, look up the X-Forwarded-For header in the request.headers setting:
1select split_part(2 current_setting('request.headers', true)::json->>'x-forwarded-for',3 ',', 1); -- takes the client IP before the first comma请查看 PostgREST 文档中的 Pre-request 和 MDN 文档中的 X-Forwarded-For。
🌐 See Pre-request in the PostgREST documentation and X-Forwarded-For in the MDN documentation.
要查看使用此请求信息的完整实现,请参阅预请求示例。
🌐 For complete implementations that use this request information, see Pre-request examples.
错误响应 #
🌐 Error responses
一个预请求函数可以引发异常来停止请求。这个例子返回一个带有 hint 和 X-Powered-By 头的 HTTP 402 付款要求响应:
🌐 A pre-request function can raise an exception to stop a request. This example returns an HTTP 402 Payment Required response with a hint and an X-Powered-By header:
1raise sqlstate 'PGRST' using2 message = json_build_object(3 'code', '123',4 'message', 'Payment Required',5 'details', 'Quota exceeded',6 'hint', 'Upgrade your plan')::text,7 detail = json_build_object(8 'status', 402,9 'headers', json_build_object(10 'X-Powered-By', 'Nerd Rage'))::text;这个异常会产生这个 HTTP 响应:
🌐 The exception produces this HTTP response:
1HTTP/1.1 402 Payment Required2Content-Type: application/json; charset=utf-83X-Powered-By: Nerd Rage45{6 "message": "Payment Required",7 "details": "Quota exceeded",8 "hint": "Upgrade your plan",9 "code": "123"10}使用 JSON 函数和操作符从异常构建动态响应。当使用自定义 HTTP 状态码(例如 419)时,在 detail 子句中包含 status_text 键。请参阅 Postgres 文档中的 JSON 函数和操作符。
🌐 Use JSON functions and operators to build dynamic responses from exceptions. Include the status_text key in the detail clause when you use a custom HTTP status code such as 419. See JSON Functions and Operators in the Postgres documentation.
对于 PostgREST 11 或更早版本,使用旧语法来抛出错误。在仪表板中检查你的 PostgREST 版本。查看 PostgREST 文档中的使用 HTTP 状态码抛出错误。
🌐 For PostgREST 11 or earlier, use the legacy syntax for raising errors. Check your PostgREST version in the Dashboard. See Raise errors with HTTP status codes in the PostgREST documentation.
配置数据 API 安全 #
🌐 Configure Data API security
本节整理了配置每个安全控制的操作步骤。请根据你的架构使用对应的步骤。
🌐 This section groups the procedures for configuring each security control. Apply the procedures that match your architecture.
明确授予访问权限 #
🌐 Grant access explicitly
除非你已授予角色对表的权限,否则无法通过数据 API 访问表。给每个角色分配其所需的最少权限。例如:
🌐 A table isn't reachable through the Data API unless you have granted a role privileges on it. Grant the minimum privileges each role needs. For example:
1-- Read-only access for anonymous clients2grant select on table public.your_table to anon;34-- Full access for signed-in users; RLS still applies5grant select, insert, update, delete on table public.your_table to authenticated;67-- Full access for server-side code using the service role8grant select, insert, update, delete on table public.your_table to service_role;910-- For functions, grant EXECUTE to the roles that should call them11grant execute on function public.your_function() to anon, authenticated;如果缺少所需的权限,PostgREST 会返回一个 42501 错误,并给出一个提示,说明你需要的确切 GRANT 语句:
🌐 If a required grant is missing, PostgREST returns a 42501 error with a hint that names the exact GRANT statement you need:
1{2 "code": "42501",3 "message": "permission denied for table your_table",4 "hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.your_table TO anon;"5}查看 Database API 42501 错误 获取完整的故障排除流程。
🌐 See Database API 42501 errors for the full troubleshooting flow.
迁移: 在同一次迁移中,将授权与 RLS 设置打包在一起。grant 命令控制角色访问。enable row level security 命令和策略控制行访问。
撤销默认权限 #
🌐 Revoke default privileges
当你希望 public 中的新对象保持不可访问,直到你授予访问权限时,撤销自动授权:
🌐 Revoke automatic grants when you want new objects in public to remain inaccessible until you grant access:
-
打开 SQL 编辑器。
-
运行以下语句:
1alter default privileges for role postgres in schema public2revoke select, insert, update, delete on tables from anon, authenticated, service_role;34alter default privileges for role postgres in schema public5revoke execute on functions from anon, authenticated, service_role;67alter default privileges for role postgres in schema public8revoke usage, select on sequences from anon, authenticated, service_role;910alter default privileges for role postgres in schema public11revoke execute on functions from public;
新的表、函数和序列现在在 Data API 角色访问之前需要明确授予权限。
🌐 New tables, functions, and sequences now require explicit grants before Data API roles can access them.
禁用数据 API #
🌐 Disable the Data API
如果你的应用从不使用 Supabase 客户端库、REST 或 GraphQL 数据端点,可以关闭数据 API :
🌐 If your app never uses Supabase client libraries, REST, or GraphQL data endpoints, turn the Data API off:
- 在仪表板中打开 数据 API 集成概览。
- 把 启用数据 API 关掉。
在禁用数据 API 的情况下,无论授权或 RLS 如何,所有自动生成的 REST 端点都不会响应。
🌐 With the Data API disabled, none of the auto-generated REST endpoints respond, regardless of grants or RLS.
启用 RLS 策略 #
🌐 Enable RLS policies
通过 Data API 暴露的没有 RLS 的表和视图,任何拥有匹配权限的角色都可以访问。启用 RLS 或添加等效控制以防止未授权访问。RLS 不适用于函数,所以只将 EXECUTE 授予需要调用它们的角色。仔细检查每个 SECURITY DEFINER 函数。
🌐 Tables and views exposed through the Data API without RLS can be accessed by any role with matching grants. Enable RLS or add equivalent controls to prevent unauthorized access. RLS doesn't apply to functions, so grant EXECUTE only to the roles that need to call them. Review every SECURITY DEFINER function carefully.
在通过数据 API 提供的每个表和视图上启用 RLS。然后,你可以编写策略,根据用户的认证令牌授予他们对特定行的访问权限。
🌐 Enable RLS on every table and view exposed through the Data API. You can then write policies that grant users access to specific rows based on their authentication token.
通过 Supabase 仪表板创建的表默认启用了 RLS。对于在 SQL 编辑器或通过其他工具创建的表,需要显式启用 RLS:
🌐 Tables created through the Supabase Dashboard have RLS enabled by default. Enable RLS explicitly for tables created in the SQL Editor or through another tool:
- 去仪表板中的 数据库 > 策略 页面。
- 选择 启用 RLS 来开启行级安全。
启用 RLS 后,制定策略来控制用户可以访问和更新哪些数据。请参见 行级安全。
🌐 With RLS enabled, create policies that control which data users can access and update. See Row Level Security.
配置一个预请求函数 #
🌐 Configure a pre-request function
创建并注册一个 Postgres 函数,在每次 Data API 请求之前运行检查:
🌐 Create and register a Postgres function to run checks before each Data API request:
在添加检查逻辑之前,先查看一下 Request information 和 Error responses。
🌐 Before adding the check logic, review Request information and Error responses.
-
创建一个预请求函数:
1create function public.check_request()2returns void3language plpgsql4security definer5as $$6begin7-- your logic here8end;9$$; -
注册该函数以在每个数据 API 请求时运行:
1alter role authenticator2set pgrst.db_pre_request = 'public.check_request'; -
重新加载 PostgREST 配置:
1notify pgrst, 'reload config';
这个函数现在会在每次数据 API 请求前运行。请添加符合你安全需求的检查。
🌐 The function now runs before every Data API request. Add the checks that match your security requirements.
请求前示例 #
🌐 Pre-request examples
在你配置好预请求函数后使用这些示例。每个示例都会用完整的请求检查替换占位逻辑。
🌐 Use these examples after you configure the pre-request function. Each example replaces the placeholder logic with a complete request check.
你只能对 POST、PUT、PATCH 和 DELETE 请求进行速率限制。GET 和 HEAD 请求是只读模式。它们可以由 Read Replicas 提供服务,而 Read Replicas 不支持向数据库写入。
🌐 You can only rate-limit POST, PUT, PATCH, and DELETE requests. GET and HEAD requests run in read-only mode. They can be served by Read Replicas, which don't support writing to the database.
结果:
private.rate_limits表记录了每个写入请求的 IP 地址和时间戳。- 当一个 IP 地址在 5 分钟内发起超过 100 次写入请求时,该功能会返回 HTTP 420 响应来拒绝请求。
创建表格:
1create table private.rate_limits (2 ip inet,3 request_at timestamp4);56-- add an index so that lookups are fast7create index rate_limits_ip_request_at_idx on private.rate_limits (ip, request_at desc);private 架构阻止 Data API 访问速率限制记录。
🌐 The private schema prevents Data API access to the rate-limit records.
创建请求检查: 创建 public.check_request 函数:
1create function public.check_request()2 returns void3 language plpgsql4 security definer5 as $$6declare7 req_method text := current_setting('request.method', true);8 req_ip inet := split_part(9 current_setting('request.headers', true)::json->>'x-forwarded-for',10 ',', 1)::inet;11 count_in_five_mins integer;12begin13 if req_method = 'GET' or req_method = 'HEAD' or req_method is null then14 -- rate limiting can't be done on GET and HEAD requests15 return;16 end if;1718 select19 count(*) into count_in_five_mins20 from private.rate_limits21 where22 ip = req_ip and request_at between now() - interval '5 minutes' and now();2324 if count_in_five_mins > 100 then25 raise sqlstate 'PGRST' using26 message = json_build_object(27 'message', 'Rate limit exceeded, try again after a while')::text,28 detail = json_build_object(29 'status', 420,30 'status_text', 'Enhance Your Calm')::text;31 end if;3233 insert into private.rate_limits (ip, request_at) values (req_ip, now());34end;35 $$;注册请求检查: 配置 public.check_request() 函数以在每次数据 API 请求时运行:
1alter role authenticator2 set pgrst.db_pre_request = 'public.check_request';34notify pgrst, 'reload config';清理旧记录: 设置一个 pg_cron 任务来删除 private.rate_limits 中的旧条目。