纲要
- 列级权限(Column-Level Privileges)
- 授权粒度与适用操作(
SELECT、INSERT、UPDATE、DELETE) - 结合视图与函数的细粒度控制
- 授权粒度与适用操作(
- 行级安全(Row-Level Security, RLS)
- 策略(
POLICY)的创建与组合 - 策略表达式与内置函数(
CURRENT_USER、CURRENT_SETTING) - 默认否定权限与绕过机制(
BYPASSRLS) WITH CHECK与USING表达式的区别
- 策略(
- 数据脱敏方案对比
- 静态脱敏工具(
pg_anonymizer、pg_dump脱敏导出) - 动态脱敏实现(视图方式 vs Rust 重构的
pg_dynamic_mask)
- 静态脱敏工具(
- 权限元数据查询(
\z与pg_class、pg_attribute) - API 速览
- 完整 Demo(基于 Node.js +
pg驱动) - 官方文档与参考链接
列级权限:精确到字段的访问控制
PostgreSQL 支持将权限细化到表的特定列,从而满足"不同用户只能访问某些敏感字段"的需求。例如,员工表中包含 employee_id、forename、salary 等列,人事专员只需读取前两列,而无权查看薪资。
列级权限适用于 SELECT、INSERT、UPDATE 操作,但 DELETE 针对整行,因此不支持列级授权。实现方式是通过 GRANT 语句指定列名:
sql
-- 创建测试表
CREATE TABLE employees (
employee_id SERIAL PRIMARY KEY,
forename TEXT,
surname TEXT,
salary NUMERIC
);
-- 创建专用角色
CREATE ROLE hr_read;
-- 仅授予对 employee_id 和 forename 的 SELECT 权限
GRANT SELECT (employee_id, forename) ON employees TO hr_read;
当 hr_read 用户执行 SELECT * FROM employees 时,会因权限不足而报错;只有显式查询被授权的列才能正常返回。若需授予更新权限,同理:
sql
GRANT UPDATE (salary) ON employees TO payroll_admin;
借助视图或函数,列级权限还可实现更复杂的转换(如对身份证号脱敏显示),但视图本身不具备列级权限的精细度,仍需底层表授权配合。
行级安全(RLS):基于行内容的动态过滤
行级安全允许定义策略(POLICY),使不同用户只能看到满足特定条件的行。典型场景如医疗信息系统:医生只能查看其负责病人的记录。
启用行级安全
默认情况下,表不启用 RLS,需显式开启:
sql
ALTER TABLE patients ENABLE ROW LEVEL SECURITY;
启用后,若未创建任何策略,则所有非表所有者或非超级用户的普通用户对该表的查询、更新、删除将返回空结果(即"默认否定权限")。这是 RLS 的核心安全原则:除非策略明确放行,否则拒绝访问。
创建策略
策略的语法为:
sql
CREATE POLICY policy_name ON table_name
[AS { PERMISSIVE | RESTRICTIVE }]
[FOR { ALL | SELECT | INSERT | UPDATE | DELETE }]
[TO { role_name | PUBLIC }]
[USING ( condition )]
[WITH CHECK ( condition )];
USING:控制哪些行可以被操作(对于SELECT、UPDATE、DELETE生效)。WITH CHECK:控制新行或修改后的行是否符合策略(对于INSERT、UPDATE生效)。
示例:让每个用户只能看到 username 等于当前会话用户的记录。
sql
CREATE TABLE test (
id SERIAL,
username TEXT,
age INT
);
ALTER TABLE test ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_policy ON test
USING (username = CURRENT_USER)
WITH CHECK (username = CURRENT_USER);
此时用户 u1 查询 test 仅返回 username = 'u1' 的行;插入时若 username 不是 'u1',则违反 WITH CHECK,操作将被拒绝。
策略组合
一个表可拥有多个策略,它们的关系由 AS 子句决定:
PERMISSIVE(默认):多个策略条件使用OR组合,任一通过即可。RESTRICTIVE:多个策略条件使用AND组合,必须全部通过。
例如,先定义一个宽松策略允许访问自己部门的数据,再加一个限制性策略禁止访问敏感标记的行。
绕过行级安全
超级用户默认拥有 BYPASSRLS 属性,可忽略所有策略。普通用户也可被授予该属性:
sql
ALTER ROLE auditor BYPASSRLS;
通过 current_setting 等函数可实现更灵活的策略,例如基于应用参数动态过滤。
数据脱敏:静态与动态方案
保护敏感信息(PII)通常需要脱敏处理。PostgreSQL 生态中常见的工具分为两类。
静态脱敏
直接修改原始数据,生成一份脱敏后的副本。代表工具:
- pg_anonymizer:CLI 工具,通过 YAML 配置文件定义规则,一键执行后原地替换数据(具备破坏性)。
- pg_dump + 脱敏导出:先导出为 SQL 文件,在导出过程中对敏感列进行替换,再导入新库,不破坏源库。
静态脱敏适合数据交付、测试环境搭建,但无法满足"同一份数据对不同用户呈现不同内容"的需求。
动态脱敏
在不改变原始数据的前提下,根据用户角色动态屏蔽或变形敏感字段。早期实现常通过视图来完成,例如:
sql
CREATE VIEW patients_view AS
SELECT
id,
name,
CASE
WHEN current_user = 'doctor_mary' THEN phone
ELSE '***'
END AS phone
FROM patients;
但视图方案维护复杂,且难以处理行级过滤。更专业的工具如 pg_dynamic_mask (前身为 pg_dynamic_mask,1.0 基于视图,2.0 用 Rust 重构)可通过策略直接作用于底层表,实现角色感知的动态脱敏,并支持多种掩码策略。
权限元数据查询
使用 \z(或 \dp)可在 psql 中查看表的权限信息:
sql
\z employees
输出中 arwdDxt 等字母表示不同权限:
a= INSERTr= SELECTw= UPDATEd= DELETED= TRUNCATEx= REFERENCESt= TRIGGER
也可查询系统表 pg_class、pg_attribute 和 pg_namespace 获取更详细的信息。
API 速览
本节汇总涉及的核心 SQL 命令与函数。
授权命令
| 命令 | 说明 | 适用版本 |
|---|---|---|
GRANT SELECT (col1, col2) ON table TO role; |
授予列级 SELECT 权限 | 8.0+ |
GRANT UPDATE (col) ON table TO role; |
授予列级 UPDATE 权限 | 8.0+ |
ALTER TABLE table ENABLE ROW LEVEL SECURITY; |
启用行级安全 | 9.5+ |
CREATE POLICY ... |
创建行安全策略 | 9.5+ |
ALTER ROLE role BYPASSRLS; |
赋予绕过 RLS 权限 | 9.5+ |
策略表达式常用函数
| 函数 | 说明 |
|---|---|
CURRENT_USER |
当前会话用户名 |
SESSION_USER |
会话初始用户名 |
CURRENT_SETTING('app.param') |
读取自定义配置参数,可用于传递上下文 |
元数据查询
sql
-- 查看表上所有策略
SELECT * FROM pg_policies WHERE tablename = 'test';
-- 查看角色属性是否包含 bypassrls
SELECT rolname, rolbypassrls FROM pg_roles;
完整 Demo:基于 Node.js 的行级安全实践
本 Demo 使用 Node.js 驱动(pg 库)演示不同数据库用户通过 RLS 自动过滤数据。
项目结构
dir
demo-rls
├── package.json
├── init.sql -- 初始化表、角色、策略
├── index.js -- 主程序
└── .env -- 数据库连接配置
初始化脚本(init.sql)
sql
-- 创建测试表
CREATE TABLE IF NOT EXISTS user_data (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
secret_info TEXT
);
-- 启用 RLS
ALTER TABLE user_data ENABLE ROW LEVEL SECURITY;
-- 创建策略:仅允许用户看到其 own 行
CREATE POLICY user_own_policy ON user_data
USING (username = CURRENT_USER)
WITH CHECK (username = CURRENT_USER);
-- 插入示例数据
INSERT INTO user_data (username, secret_info) VALUES
('alice', 'Alice secret'),
('bob', 'Bob secret'),
('charlie', 'Charlie secret');
-- 创建数据库角色(与系统用户对应)
CREATE ROLE alice LOGIN PASSWORD 'alice123';
CREATE ROLE bob LOGIN PASSWORD 'bob123';
GRANT SELECT, INSERT, UPDATE, DELETE ON user_data TO alice, bob;
GRANT USAGE ON SEQUENCE user_data_id_seq TO alice, bob;
Node.js 主程序(index.js)
使用 pg 连接池,模拟多用户并发查询。
js
const { Pool } = require('pg');
// 连接配置(超级用户用于创建连接,实际使用时可切换角色)
const superPool = new Pool({
host: 'localhost',
port: 5432,
database: 'testdb',
user: 'postgres',
password: 'postgres'
});
// 模拟不同用户的查询
async function queryAsUser(username, password) {
const pool = new Pool({
host: 'localhost',
port: 5432,
database: 'testdb',
user: username,
password: password
});
try {
const res = await pool.query('SELECT * FROM user_data ORDER BY id');
console.log(`--- Data for ${username} ---`);
res.rows.forEach(row => console.log(row));
} finally {
await pool.end();
}
}
async function runDemo() {
// 先以超级用户查看全部
console.log('--- Superuser sees all ---');
const all = await superPool.query('SELECT * FROM user_data');
all.rows.forEach(row => console.log(row));
// 分别以 alice 和 bob 查询
await queryAsUser('alice', 'alice123');
await queryAsUser('bob', 'bob123');
await superPool.end();
}
runDemo().catch(console.error);
运行说明
- 创建 PostgreSQL 数据库
testdb,执行init.sql初始化。 - 安装依赖:
npm init -y && npm install pg dotenv。 - 配置
.env或直接修改连接信息。 - 运行
node index.js。
输出预期
- 超级用户能看到所有行(
alice、bob、charlie)。 alice用户仅能看到username = 'alice'的行。bob用户仅能看到username = 'bob'的行。
技术点总结
- 行级安全策略的启用与创建。
- 多用户角色隔离查询。
- 通过
CURRENT_USER实现动态过滤。 - 超级用户绕过策略的能力。
项目难点与解决方案(针对 RLS 生产落地)
核心难点
- 策略的默认否定行为导致普通用户初始无法访问,需谨慎设计。
- 多个策略的
PERMISSIVE/RESTRICTIVE组合容易产生逻辑混淆。 - 动态脱敏与行级过滤的结合需要额外函数支持。
解决方案
- 分阶段上线:先启用 RLS 但创建宽松策略(如
USING (true))保证现有业务不中断,再逐步收紧。 - 明确策略优先级,统一使用
RESTRICTIVE作为全局黑名单,PERMISSIVE作为白名单。 - 采用
CURRENT_SETTING传递应用层用户属性,实现更细粒度的动态策略。
广度
- 覆盖 SQL 权限体系、数据脱敏工具、元数据查询。
深度
- 深入策略表达式的执行机制,区分
USING与WITH CHECK的不同阶段。
复杂度
- 多租户场景下策略可能多达数十个,需结合
pg_policies视图进行审计。
官方文档
- PostgreSQL 官方文档(行级安全):https://www.postgresql.org/docs/current/ddl-rowsecurity.html
- 权限授予:https://www.postgresql.org/docs/current/sql-grant.html
- 策略创建:https://www.postgresql.org/docs/current/sql-createpolicy.html
参考链接
pg_anonymizer项目:https://github.com/raphaelauv/pg_anonymizerpg_dynamic_mask介绍(Rust 重构):https://github.com/daamien/pg_dynamic_mask- 列级权限实践示例:https://wiki.postgresql.org/wiki/Column_level_privileges
总结
PostgreSQL 提供了列级权限与行级安全两大精细控制机制,分别作用于字段和记录层面,为多租户应用、隐私合规场景提供了原生支持。列级权限通过 GRANT 指定列名实现,而行级安全则依赖策略表达式,并配合 USING / WITH CHECK 完成读写过滤。
结合静态或动态脱敏工具,可构建完整的敏感数据保护体系。实际部署时需注意默认否定行为、策略组合规则以及超级用户的绕过权限,以确保业务平滑迁移。