数据库授权

数据库授权是授予和验证数据库访问权限的过程。PostgreSQL 使用角色的概念来管理权限。

用户和组

角色可以被认为是数据库用户或数据库用户组,具体取决于角色的设置方式。

每个 Web 用户的角色

PostgREST 可以适应这两种观点。如果您将角色视为单个用户,那么 基于 JWT 的用户模拟 实现了您所需的大部分功能。当经过身份验证的用户发出请求时,PostgREST 将切换到该用户的数据库角色,除了限制查询之外,该角色还可通过 current_user 变量在 SQL 中使用。

您可以使用行级安全灵活地限制当前用户的可见性和访问权限。这里有一个来自 Tomas Vondra 的 示例,一个聊天表存储用户之间发送的消息。用户可以向其中插入行以向其他用户发送消息,并查询它以查看其他用户发送给他们的消息。

CREATE TABLE chat (
  message_uuid    UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  message_time    TIMESTAMP NOT NULL DEFAULT now(),
  message_from    NAME      NOT NULL DEFAULT current_user,
  message_to      NAME      NOT NULL,
  message_subject VARCHAR(64) NOT NULL,
  message_body    TEXT
);

ALTER TABLE chat ENABLE ROW LEVEL SECURITY;

我们希望实施一项策略,确保用户只能看到他们发送或发送给他们自己的消息。我们还希望阻止用户伪造 message_from 列,使其成为其他人的姓名。

PostgreSQL 允许我们使用行级安全设置此策略

CREATE POLICY chat_policy ON chat
  USING ((message_to = current_user) OR (message_from = current_user))
  WITH CHECK (message_from = current_user)

任何访问聊天表生成的 API 端点的人都会看到他们应该看到的行,而无需我们进行自定义的命令式服务器端编码。

警告

角色是按集群命名空间的,而不是按数据库命名空间的,因此它们可能容易发生冲突。

Web 用户共享角色

或者,数据库角色可以代表组,而不是(或除了)单个用户。您可以选择所有登录的 Web 应用程序用户共享角色 webuser。您可以通过在 JWT 中包含额外的声明(如电子邮件)来区分单个用户。

{
  "role": "webuser",
  "email": "john@doe.com"
}

SQL 代码可以通过 PostgREST 事务范围设置 访问声明。例如,要获取电子邮件声明,请调用此函数

current_setting('request.jwt.claims', true)::json->>'email';

注意

对于 PostgreSQL < 14

current_setting('request.jwt.claim.email', true);

这允许 JWT 生成服务包含额外信息,您的数据库代码可以对其做出反应。例如,RLS 示例可以修改为使用此 current_setting 而不是 current_user。第二个 'true' 参数告诉 current_setting 如果设置在当前配置中缺失,则返回 NULL。

混合用户组角色

您可以混合组和单个角色策略。例如,我们仍然可以拥有一个 webuser 角色和从它继承的单个用户

CREATE ROLE webuser NOLOGIN;
-- grant this role access to certain tables etc

CREATE ROLE user000 NOLOGIN;
GRANT webuser TO user000;
-- now user000 can do whatever webuser can

GRANT user000 TO authenticator;
-- allow authenticator to switch into user000 role
-- (the role itself has nologin)

模式

您必须在 db-schemas 中明确允许角色访问公开的模式。

GRANT USAGE ON SCHEMA api TO webuser;

要让 Web 用户访问表,您必须授予他们对您希望他们执行的操作的权限。

GRANT
  SELECT
, INSERT
, UPDATE(message_body)
, DELETE
ON chat TO webuser;

您还可以选择操作在哪个表列上有效。在上面的示例中,Web 用户只能更新 message_body 列。

函数

默认情况下,当创建函数时,执行它的权限不受角色限制。函数访问权限为 PUBLIC - 可由所有角色执行(有关详细信息,请参阅 PostgreSQL 权限页面)。这对于 API 模式来说并不理想。要禁用此行为,您可以运行以下 SQL 语句

ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;

这将更改将来在所有模式中创建的所有函数的权限。目前无法将其限制为单个模式。在我们看来,这始终是一个好习惯。

注意

但是,可以将此子句的效果限制为您定义的函数。您可以在 API 模式定义的开头放置上面的语句,然后在结尾处使用以下语句将其反转

ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO PUBLIC;

这将起作用,因为 alter default privileges 语句对之后执行的函数创建有效。有关详细信息,请参阅 PostgreSQL 更改默认权限

之后,您需要显式授予函数的 EXECUTE 权限

GRANT EXECUTE ON FUNCTION login TO anonymous;
GRANT EXECUTE ON FUNCTION signup TO anonymous;

您也可以授予更高权限角色对架构中所有函数的执行权限。

GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA api TO web_user;

安全定义者

函数以调用者的权限执行。这意味着用户必须拥有执行函数执行操作的所有权限。如果函数访问私有数据库对象,您的 API 角色 将无法成功执行该函数。

另一种选择是使用 SECURITY DEFINER 选项定义函数。然后,将只进行一次权限检查,即调用函数的权限,函数中的操作将拥有函数所有者的权限。

-- login as a user wich has privileges on the private schemas

-- create a sample function
create or replace function login(email text, pass text, out token text) as $$
begin
  -- access to a private schema called 'auth'
  select auth.user_role(email, pass) into _role;
  -- other operations
  -- ...
end;
$$ language plpgsql security definer;

请注意函数末尾的 SECURITY DEFINER 关键字。有关更多详细信息,请参阅 PostgreSQL 文档

视图

视图以视图所有者的权限调用,类似于使用 SECURITY DEFINER 选项的函数。当由 SUPERUSER 角色创建时,所有 行级安全 策略将被绕过。

如果您使用的是 PostgreSQL >= 15,可以通过指定 security_invoker 选项来更改此行为。

CREATE VIEW sample_view WITH (security_invoker = true) AS
SELECT * FROM sample_table;

在 PostgreSQL < 15 上,您可以创建一个非 SUPERUSER 角色,并将其设置为视图的所有者。

CREATE ROLE api_views_owner NOSUPERUSER NOBYPASSRLS;
ALTER VIEW sample_view OWNER TO api_views_owner;