↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5
预发布版本文档。 PostgreSQL 19beta4 为测试版本,最终发布内容可能有所不同。

CREATE POLICY

CREATE POLICY — 为一个表定义一条新的行级安全策略

大纲

CREATE POLICY name ON table_name
    [ AS { PERMISSIVE | RESTRICTIVE } ]
    [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
    [ TO { role_name | PUBLIC | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] ]
    [ USING ( using_expression ) ]
    [ WITH CHECK ( check_expression ) ]

描述

CREATE POLICY 命令为一个表定义一条新的行级安全策略。请注意,必须先在该表上启用行级安全(使用 ALTER TABLE ... ENABLE ROW LEVEL SECURITY),已创建的策略才会被应用。

策略允许对符合相关策略表达式的行进行选择、插入、更新或删除。现有表行会根据 USING 中指定的表达式进行检查,而将通过 INSERT 或 UPDATE 创建的新行则会根据 WITH CHECK 中指定的表达式进行检查。当 USING 表达式对给定行返回真时,该行对用户可见;如果返回假或 null,则该行不可见。通常,当某行不可见时不会报错,但也有例外,见表 307。当 WITH CHECK 表达式对某行返回真时,该行会被插入或更新;如果返回假或 null,则会报错。

对于 INSERT、UPDATE 和 MERGE 语句,WITH CHECK 表达式会在 BEFORE 触发器触发后、在进行任何实际数据修改之前强制执行。因此,BEFORE ROW 触发器可以修改待插入的数据,从而影响安全策略检查的结果。WITH CHECK 表达式会在任何其他约束之前执行。

策略名称按表区分。因此,同一个策略名可以用于许多不同的表,并且在每个表上都可以有适合该表的定义。

策略可以针对特定命令或特定角色应用。除非另有指定,新建策略默认适用于所有命令和角色。多个策略可以应用于同一命令;更多细节见下文。表 307 总结了不同类型的策略如何应用于特定命令。

对于既可以具有 USING 又可以具有 WITH CHECK 表达式的策略(ALL 和 UPDATE),如果未定义 WITH CHECK 表达式,那么 USING 表达式将同时用于决定哪些行可见(普通 USING 情形)以及允许写入哪些新行(WITH CHECK 情形)。

如果某个表启用了行级安全,但不存在适用的策略,则会假定存在一条“默认拒绝”策略,因此没有任何行可见或可更新。

参数

name #

要创建的策略名称。它必须不同于该表上任何其他策略的名称。

table_name #

该策略适用的表的名称(可选模式限定)。

PERMISSIVE #

指定将该策略创建为宽松策略。适用于给定查询的所有宽松策略都会使用布尔“OR”操作符组合在一起。通过创建宽松策略,管理员可以扩大可访问行的集合。策略默认是宽松的。

RESTRICTIVE #

指定将该策略创建为限制性策略。适用于给定查询的所有限制性策略都会使用布尔“AND”操作符组合在一起。通过创建限制性策略,管理员可以缩小可访问行的集合,因为每一行都必须通过所有限制性策略。

请注意,要让限制性策略能够有效缩小访问范围,必须先至少有一条宽松策略授予对行的访问。如果只存在限制性策略,则没有任何行可访问。当宽松策略和限制性策略混合存在时,只有在至少一条宽松策略通过且所有限制性策略也都通过时,某一行才可访问。

command #

该策略适用的命令。有效选项是 ALL、SELECT、INSERT、UPDATE 和 DELETE。ALL 是默认值。有关这些策略如何应用的细节见下文。

role_name #

该策略适用的角色(或多个角色)。默认是 PUBLIC,即对所有角色应用该策略。

using_expression #

任意返回 boolean 的 SQL 条件表达式。该条件表达式不能包含任何聚合函数或窗口函数。如果启用了行级安全,该表达式会被添加到引用该表的查询中。表达式返回真的行将对用户可见。表达式返回假或 null 的行在 SELECT 中对用户不可见,在 UPDATE 或 DELETE 中也不能用于修改。通常,这类行会被静默忽略,不会报告错误(但例外情况见表 307)。

check_expression #

任意返回 boolean 的 SQL 条件表达式。该条件表达式不能包含任何聚合函数或窗口函数。如果启用了行级安全,该表达式将用于针对该表的 INSERT 和 UPDATE 查询。只有使该表达式求值为真的行才会被允许。对任何被插入的行,或更新后产生的任何行,如果该表达式求值为假或 null,就会抛出错误。请注意,check_expression 是针对该行拟写入的新内容而不是原始内容求值的。

针对每种命令的策略

ALL #

对策略使用 ALL 意味着无论命令类型如何,该策略都适用于所有命令。如果存在 ALL 策略且还存在更具体的策略,则 ALL 策略和更具体的策略(或多个策略)都会被应用。此外,ALL 策略会同时应用于查询的选择端和修改端,并在两端使用 USING 表达式(如果只定义了 USING 表达式)。

例如,如果发出 UPDATE,那么 ALL 策略既适用于 UPDATE 能够选出作为更新目标的行(应用 USING 表达式),也适用于更新后的结果行,以检查它们是否允许被写入该表(若定义了 WITH CHECK 表达式则应用之,否则应用 USING 表达式)。如果 INSERT 或 UPDATE 命令试图向表中添加未通过 ALL 策略的 WITH CHECK 表达式(若未定义 WITH CHECK 表达式,则为其 USING 表达式)的行,整个命令将被中止。

SELECT #

对策略使用 SELECT,意味着它适用于 SELECT 查询,以及在其定义所在关系上需要 SELECT 权限的任何场景。结果是,只有通过 SELECT 策略的那些行才会在 SELECT 查询中返回,而像 UPDATE、DELETE 和 MERGE 这类需要 SELECT 权限的查询,也只能看到 SELECT 策略允许的那些行。SELECT 策略不能带有 WITH CHECK 表达式,因为它只适用于从关系中取回行的情况,下述情形除外。

如果某个修改数据的查询带有 RETURNING 子句,则该关系上需要 SELECT 权限,并且该关系中新插入或更新的任何行都必须满足该关系的 SELECT 策略,才能提供给 RETURNING 子句。如果新插入或更新的行不满足该关系的 SELECT 策略,就会抛出错误(需要返回的插入或更新行绝不会被静默忽略)。

如果 INSERT 带有 ON CONFLICT DO SELECT/UPDATE 子句,或带有指定仲裁索引或约束的 ON CONFLICT DO NOTHING 子句,那么该关系上也需要 SELECT 权限,并且提议插入的行会按照该关系的 SELECT 策略进行检查。如果某个提议插入的行不满足该关系的 SELECT 策略,就会抛出错误(INSERT 绝不会被静默跳过)。此外,如果走的是 UPDATE 路径,则待更新的行以及更新后的新行都会按照该关系的 SELECT 策略进行检查;如果不满足,同样会报错(辅助的 UPDATE 绝不会被静默跳过)。

MERGE 命令要求在源关系和目标关系上都具有 SELECT 权限,因此每个关系的 SELECT 策略都会在连接它们之前被应用,而 MERGE 动作也只能看到这些策略允许的行。另外,如果执行了 UPDATE 动作,则目标关系的 SELECT 策略会像独立的 UPDATE 那样应用到更新后的行,但不同之处在于,不满足这些策略时会报错。

INSERT #

对策略使用 INSERT,意味着它将适用于 INSERT 命令,以及包含 INSERT 动作的 MERGE 命令。插入的行如果未通过该策略,将导致策略违规错误,并且整个 INSERT 命令将被中止。INSERT 策略不能带有 USING 表达式,因为它只适用于向关系添加行的情况。

注意,带有 ON CONFLICT 子句的 INSERT,会对所有提议插入的行检查 INSERT 策略的 WITH CHECK 表达式,无论这些行最终是否真的被插入。

UPDATE #

对策略使用 UPDATE,意味着它将适用于 UPDATE 和 SELECT FOR UPDATE/SHARE 命令,以及 INSERT 命令中辅助的 ON CONFLICT DO UPDATE 和 ON CONFLICT DO SELECT FOR UPDATE/SHARE 子句,还适用于包含 UPDATE 动作的 MERGE 命令。由于 UPDATE 命令需要取出现有行并用修改后的新行替换它,因此 UPDATE 策略同时接受 USING 表达式和 WITH CHECK 表达式。USING 表达式决定 UPDATE 命令能够看到哪些行来执行操作,而 WITH CHECK 表达式则定义哪些修改后的行允许被写回该关系。

任何更新后的值未通过 WITH CHECK 表达式的行都会导致错误,并且整个命令将被中止。如果只指定了一个 USING 子句,那么该子句将被用于 USING 和 WITH CHECK 两种情况。

通常,UPDATE 命令还需要从待更新关系的列中读取数据(例如在 WHERE 子句、RETURNING 子句,或 SET 子句右侧的表达式中)。这种情况下,正在被更新的关系上也需要 SELECT 权限,并且除了 UPDATE 策略外,还会应用适当的 SELECT 或 ALL 策略。这样,用户除了必须通过 UPDATE 或 ALL 策略获准更新这些行之外,还必须通过 SELECT 或 ALL 策略访问正在被更新的行。

当 INSERT 命令带有辅助的 ON CONFLICT DO UPDATE 子句时,如果走 UPDATE 路径,则待更新的行会先根据各适用的 UPDATE 策略的 USING 表达式进行检查,然后新的更新后行会再根据 WITH CHECK 表达式进行检查。但要注意,与独立的 UPDATE 命令不同,如果现有行没有通过 USING 表达式检查,就会报错(UPDATE 路径绝不会被静默跳过)。对 MERGE 命令中的 UPDATE 动作也是如此。

DELETE #

对策略使用 DELETE,意味着它将适用于 DELETE 命令,以及包含 DELETE 动作的 MERGE 命令。对于 DELETE 命令,只有通过该策略的行才会被 DELETE 命令看到。可能存在一些通过 SELECT 策略可见,但由于未通过 DELETE 策略的 USING 表达式而不能删除的行。但请注意,在 MERGE 命令中的 DELETE 动作会看到通过 SELECT 策略可见的行;如果某一行未通过 DELETE 策略,就会报错。

在多数情况下,DELETE 命令也需要从其删除所针对的关系中的列读取数据(例如在 WHERE 子句或 RETURNING 子句中)。这种情况下,该关系上也需要 SELECT 权限,并且除了 DELETE 策略外,还会应用适当的 SELECT 或 ALL 策略。这样,用户除了必须通过 DELETE 或 ALL 策略获准删除这些行之外,还必须通过 SELECT 或 ALL 策略访问正在被删除的行。

DELETE 策略不能具有 WITH CHECK 表达式,因为它只适用于正在从关系中删除行的情况,所以没有新行需要检查。

表 307 总结了不同类型的策略如何应用到特定命令上。在该表中,“检查”表示要检查策略表达式,若其返回假或 null 则抛出错误;而“筛选”表示若策略表达式返回假或 null,则该行会被静默忽略。

表 307. 按命令类型应用的策略

命令SELECT/ALL策略INSERT/ALL策略UPDATE/ALL策略DELETE/ALL策略
USING 表达式WITH CHECK 表达式USING 表达式WITH CHECK 表达式USING 表达式
SELECT / COPY ... TO筛选现有行————
SELECT FOR UPDATE/SHARE筛选现有行—筛选现有行——
INSERT 检查新行 [a] 检查新行———
UPDATE 筛选现有行 [a] 并检查新行 [a] —筛选现有行检查新行—
DELETE 筛选现有行 [a] ———筛选现有行
INSERT ... ON CONFLICT 检查新行 [b][c] 检查新行 [c] ———
ON CONFLICT DO UPDATE 检查现有行和新行 [d] —检查现有行 检查新行 [d] —
ON CONFLICT DO SELECT检查现有行————
ON CONFLICT DO SELECT FOR UPDATE/SHARE检查现有行—检查现有行——
MERGE筛选源行和目标行————
MERGE ... THEN INSERT 检查新行 [a] 检查新行———
MERGE ... THEN UPDATE检查新行—检查现有行检查新行—
MERGE ... THEN DELETE————检查现有行

[a] 如果需要读取现有行或新行,例如在 WHERE 或 RETURNING 子句引用该关系的列时。

[b] 如果指定了仲裁索引或约束。

[c] 无论最终是否实际发生冲突,提议插入的行都会被检查。

[d] 指辅助 UPDATE 命令中的新行,它可能与原始 INSERT 命令中的新行不同。


多条策略的应用

当不同命令类型的多条策略应用于同一命令时(例如 SELECT 和 UPDATE 策略应用于 UPDATE 命令),用户必须同时具有这两种权限(例如既有从该关系中选取行的权限,也有更新这些行的权限)。因此,一种策略类型的表达式会与另一种策略类型的表达式使用 AND 操作符组合。

当同一命令类型的多条策略应用于同一命令时,必须至少有一条 PERMISSIVE 策略授予对该关系的访问权,并且所有 RESTRICTIVE 策略都必须通过。因此,所有 PERMISSIVE 策略表达式使用 OR 组合,所有 RESTRICTIVE 策略表达式使用 AND 组合,然后再将两者的结果使用 AND 组合。如果没有 PERMISSIVE 策略,则访问被拒绝。

请注意,就组合多条策略而言,ALL 策略会被视为与当前正在应用的其他策略同一类型。

例如,对于一个同时需要 SELECT 和 UPDATE 权限的 UPDATE 命令,如果这两类策略各自都有多个适用项,它们会按以下方式组合:

RESTRICTIVE SELECT/ALL 策略 1 中的 表达式
AND
RESTRICTIVE SELECT/ALL 策略 2 中的 表达式
AND
...
AND
(
  PERMISSIVE SELECT/ALL 策略 1 中的 表达式
  OR
  PERMISSIVE SELECT/ALL 策略 2 中的 表达式
  OR
  ...
)
AND
RESTRICTIVE UPDATE/ALL 策略 1 中的 表达式
AND
RESTRICTIVE UPDATE/ALL 策略 2 中的 表达式
AND
...
AND
(
  PERMISSIVE UPDATE/ALL 策略 1 中的 表达式
  OR
  PERMISSIVE UPDATE/ALL 策略 2 中的 表达式
  OR
  ...
)

注解

要为一个表创建或修改策略,你必须是该表的拥有者。

虽然策略会应用于针对数据库中表的显式查询,但当系统执行内部引用完整性检查或验证约束时,并不会应用这些策略。这意味着仍然存在间接判断某个给定值是否存在的方法。一个例子是,尝试向某个主键列或带有唯一约束的列插入重复值。如果插入失败,用户就能推断该值已经存在。(这个例子假定策略允许该用户插入自己无权看见的行。)另一个例子是,用户被允许向一个引用了另一张表的表中插入数据,而被引用的那张表本身对其是隐藏的。用户可以通过向引用表插入值来判断其存在性;插入成功就表示该值存在于被引用表中。要解决这些问题,可以仔细设计策略,防止用户插入、删除或更新那些可能暗示其本来无权看见的值是否存在的行,或者改用生成的值(例如代理键)来代替具有外部含义的键。

通常,为了防止受保护的数据无意间暴露给可能不可信的用户定义函数,系统会在应用用户查询中出现的条件之前,先强制执行安全策略施加的过滤条件。不过,被系统(或系统管理员)标记为 LEAKPROOF 的函数和操作符由于被假定为可信,可以在策略表达式之前求值。

由于策略表达式会被直接添加到用户查询中,因此它们将以执行整个查询的用户权限运行。因此,使用某条策略的用户必须能够访问表达式中引用的所有表或函数,否则在尝试查询启用了行级安全的表时,只会收到权限被拒绝的错误。不过,这并不改变视图的工作方式。与普通查询和视图一样,被视图引用的表的权限检查和策略将使用视图所有者的权限,以及适用于视图所有者的任何策略;但如果视图是用 security_invoker 选项定义的,则例外(见 CREATE VIEW)。

对于 MERGE,不存在单独的策略。相反,执行 MERGE 时会根据实际执行的动作,应用为 SELECT、INSERT、UPDATE 和 DELETE 定义的策略。

更多讨论和实际示例见第 5.10 节。

兼容性

CREATE POLICY 是一种 PostgreSQL 扩展。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.