PostgreSQL笔记59:行列级权限机制与数据脱敏实践

纲要

  • 列级权限(Column-Level Privileges)
    • 授权粒度与适用操作(SELECTINSERTUPDATEDELETE
    • 结合视图与函数的细粒度控制
  • 行级安全(Row-Level Security, RLS)
    • 策略(POLICY)的创建与组合
    • 策略表达式与内置函数(CURRENT_USERCURRENT_SETTING
    • 默认否定权限与绕过机制(BYPASSRLS
    • WITH CHECKUSING 表达式的区别
  • 数据脱敏方案对比
    • 静态脱敏工具(pg_anonymizerpg_dump 脱敏导出)
    • 动态脱敏实现(视图方式 vs Rust 重构的 pg_dynamic_mask
  • 权限元数据查询(\zpg_classpg_attribute
  • API 速览
  • 完整 Demo(基于 Node.js + pg 驱动)
  • 官方文档与参考链接

列级权限:精确到字段的访问控制

PostgreSQL 支持将权限细化到表的特定列,从而满足"不同用户只能访问某些敏感字段"的需求。例如,员工表中包含 employee_idforenamesalary 等列,人事专员只需读取前两列,而无权查看薪资。

列级权限适用于 SELECTINSERTUPDATE 操作,但 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:控制哪些行可以被操作(对于 SELECTUPDATEDELETE 生效)。
  • WITH CHECK:控制新行或修改后的行是否符合策略(对于 INSERTUPDATE 生效)。

示例:让每个用户只能看到 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 = INSERT
  • r = SELECT
  • w = UPDATE
  • d = DELETE
  • D = TRUNCATE
  • x = REFERENCES
  • t = TRIGGER

也可查询系统表 pg_classpg_attributepg_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);

运行说明

  1. 创建 PostgreSQL 数据库 testdb,执行 init.sql 初始化。
  2. 安装依赖:npm init -y && npm install pg dotenv
  3. 配置 .env 或直接修改连接信息。
  4. 运行 node index.js

输出预期

  • 超级用户能看到所有行(alicebobcharlie)。
  • alice 用户仅能看到 username = 'alice' 的行。
  • bob 用户仅能看到 username = 'bob' 的行。

技术点总结

  • 行级安全策略的启用与创建。
  • 多用户角色隔离查询。
  • 通过 CURRENT_USER 实现动态过滤。
  • 超级用户绕过策略的能力。

项目难点与解决方案(针对 RLS 生产落地)

核心难点

  • 策略的默认否定行为导致普通用户初始无法访问,需谨慎设计。
  • 多个策略的 PERMISSIVE / RESTRICTIVE 组合容易产生逻辑混淆。
  • 动态脱敏与行级过滤的结合需要额外函数支持。

解决方案

  • 分阶段上线:先启用 RLS 但创建宽松策略(如 USING (true))保证现有业务不中断,再逐步收紧。
  • 明确策略优先级,统一使用 RESTRICTIVE 作为全局黑名单,PERMISSIVE 作为白名单。
  • 采用 CURRENT_SETTING 传递应用层用户属性,实现更细粒度的动态策略。

广度

  • 覆盖 SQL 权限体系、数据脱敏工具、元数据查询。

深度

  • 深入策略表达式的执行机制,区分 USINGWITH CHECK 的不同阶段。

复杂度

  • 多租户场景下策略可能多达数十个,需结合 pg_policies 视图进行审计。

官方文档

参考链接

总结

PostgreSQL 提供了列级权限与行级安全两大精细控制机制,分别作用于字段和记录层面,为多租户应用、隐私合规场景提供了原生支持。列级权限通过 GRANT 指定列名实现,而行级安全则依赖策略表达式,并配合 USING / WITH CHECK 完成读写过滤。

结合静态或动态脱敏工具,可构建完整的敏感数据保护体系。实际部署时需注意默认否定行为、策略组合规则以及超级用户的绕过权限,以确保业务平滑迁移。

相关推荐
疯狂打码的少年2 小时前
【数据结构】选择类排序:简单选择与堆排序
java·数据结构·笔记·算法
那年窗外下的雪.2 小时前
Linux 学习笔记
linux·笔记·学习
爱奥尼欧2 小时前
【daily practice】最长数字串怎么找?岛屿数量如何沉岛秒数?六棍拼两三角形有几种验法?(5.字符串中找出连续最长的数字串、岛屿数量、拼三角)
笔记·学习
tachibana22 小时前
把RAGAS跑起来
数据库·人工智能·ai·架构·大模型·llm·rag
疯狂打码的少年2 小时前
【数据结构】排序算法:归并排序与基数排序
数据结构·笔记·算法·排序算法
蒸蒸yyyyzwd2 小时前
cpp选手秋招学习笔记day14
数据库·oracle
数据库小学妹3 小时前
MySQL 8.4还是9.7 LTS?8.0 EOL后升级怎么选
数据库·mysql·dba·版本升级·数据库运维
存在morning3 小时前
【PySpark 学习笔记 三】DataFrame API 入门
android·java·数据库
m0_547486663 小时前
《数据库原理及应用教程MySQL 8.0》全套PPT课件2026
数据库·mysql