审计访问权限
追踪谁可以查看哪些内容
审计访问权限 是 CoddyKit 上的免费 SQL Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 SQL Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 SQL Academy 课程共包含 4 节课。
为什么要审计访问
了解谁在何时访问了哪些数据,是数据库安全的基石。审计会创建可靠的事件记录,帮助您检测未经授权的访问、调查事件,并满足 GDPR、HIPAA 或 SOC 2 等合规要求。
在本课中,您将学习如何设计审计表、使用触发器自动捕获访问事件、使用 PostgreSQL 内置的日志记录功能,以及查询审计记录来回答“谁能看到什么?”这一问题。
设计审计日志表
第一步是创建一张专用表,用于记录每个重要事件。一份良好的审计日志会存储表名、操作类型、旧值和新值、执行操作的用户,以及精确的时间戳。
下面的示例创建了一张通用的 audit_log 表,并使用 JSONB 列存储行快照——无需修改架构即可灵活处理任何表。
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
event_time TIMESTAMPTZ NOT NULL DEFAULT now(),
db_user TEXT NOT NULL DEFAULT current_user,
app_user TEXT,
table_name TEXT NOT NULL,
operation TEXT NOT NULL CHECK (operation IN ('INSERT','UPDATE','DELETE','SELECT')),
row_id BIGINT,
old_data JSONB,
new_data JSONB
);记录当前用户
PostgreSQL 提供多个内置函数,用于识别正在执行查询的用户。current_user 返回任何 SET ROLE 执行后生效的角色名称。无论是否切换角色,session_user 始终返回最初登录的角色。
对于使用单个共享数据库角色、但通过 SET LOCAL app.current_user 传递应用层用户的应用程序,您可以使用 current_setting() 读取该设置。
-- Who is the database user right now?
SELECT current_user,
session_user;
-- Read an application-level user injected by the app layer
SELECT current_setting('app.current_user', true) AS app_user;编写审计触发器函数
触发器函数是捕获数据变更事件最可靠的方式,因为它会自动触发——任何应用程序代码都无法绕过它。下面的函数会记录其附加到的任何表上的每个 INSERT、UPDATE 和 DELETE,并将旧行值和新行值存储为 JSONB。
请注意 TG_TABLE_NAME(触发触发器的表)以及使用 row_to_json() 将行值转换为可存储格式。
CREATE OR REPLACE FUNCTION fn_audit_changes()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
INSERT INTO audit_log (
db_user,
app_user,
table_name,
operation,
row_id,
old_data,
new_data
) VALUES (
current_user,
current_setting('app.current_user', true),
TG_TABLE_NAME,
TG_OP,
COALESCE(NEW.id, OLD.id),
CASE WHEN TG_OP = 'INSERT' THEN NULL ELSE row_to_json(OLD)::JSONB END,
CASE WHEN TG_OP = 'DELETE' THEN NULL ELSE row_to_json(NEW)::JSONB END
);
RETURN NULL;
END;
$$;将触发器附加到表
触发器函数创建后,您可以使用 CREATE TRIGGER 语句将它附加到每张需要审计的表。使用 AFTER 可确保数据确实已经写入,然后再创建日志条目。FOR EACH ROW 子句会在每个被修改的行上触发一次触发器。
这里将触发器应用于假设存在的 patients 表,自动记录每个 INSERT、UPDATE 和 DELETE。
CREATE TRIGGER trg_audit_patients
AFTER INSERT OR UPDATE OR DELETE
ON patients
FOR EACH ROW
EXECUTE FUNCTION fn_audit_changes();审计 SELECT 查询
数据变更触发器只能捕获写入操作。要审计读取访问,您需要采用不同的方法。一种选择是使用 AFTER SELECT 语句级触发器(PostgreSQL 14+ 在特定上下文中支持)。更常见的模式是在包装敏感表的函数或视图中显式记录读取操作。
下面的示例使用一个函数包装敏感表,在返回结果之前记录每次读取。
CREATE OR REPLACE FUNCTION get_patient_record(p_id INT)
RETURNS SETOF patients
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- Log the read access
INSERT INTO audit_log (db_user, app_user, table_name, operation, row_id)
VALUES (
current_user,
current_setting('app.current_user', true),
'patients',
'SELECT',
p_id
);
RETURN QUERY
SELECT * FROM patients WHERE id = p_id;
END;
$$;PostgreSQL 内置日志记录
PostgreSQL 的 postgresql.conf 提供了强大的服务器端日志记录功能,无需编写应用程序代码。设置 log_min_duration_statement 可记录任何超过阈值的查询。设置 log_connections 和 log_disconnections 可记录用户的登录和退出。
下面的查询使用 pg_stat_activity 系统视图查看当前处于活动状态的会话,这是一种轻量级的实时访问监控方式。
-- See who is currently connected and what they are running
SELECT pid,
usename AS db_user,
application_name,
client_addr,
state,
query_start,
LEFT(query, 80) AS current_query
FROM pg_stat_activity
WHERE datname = current_database()
ORDER BY query_start DESC;查询审计日志
只有能够有效查询,审计日志才有价值。常见问题包括:最近访问过某条记录的是哪个用户、某一行随时间发生了哪些变化,以及过去 24 小时内发生了多少次敏感数据读取。
下面的查询会找出所有访问过特定患者记录的用户,并按最近访问时间从近到远排序。
SELECT event_time,
db_user,
app_user,
operation,
old_data,
new_data
FROM audit_log
WHERE table_name = 'patients'
AND row_id = 42
ORDER BY event_time DESC
LIMIT 20;检测可疑访问模式
收集审计数据后,您可以编写查询来标记异常。例如,某个用户突然读取的行数远超平时,或者同一条敏感记录在很短的时间窗口内被多次访问,这些情况都可能表明有人试图窃取数据。
下面的查询统计过去一小时内每个应用用户执行的 SELECT 事件数量,并突出显示读取行数超过 100 的用户。
SELECT app_user,
COUNT(*) AS records_accessed
FROM audit_log
WHERE operation = 'SELECT'
AND event_time >= now() - INTERVAL '1 hour'
GROUP BY app_user
HAVING COUNT(*) > 100
ORDER BY records_accessed DESC;保护审计日志本身
可以被修改的审计日志并不可信。您应锁定它,防止普通用户和应用角色对行执行 DELETE 或 UPDATE 操作。最安全的做法是仅向应用角色授予 INSERT 权限,并将 SELECT 权限保留给专用审计角色。
您还可以通过触发器强制实现不可变性:如果有人尝试 UPDATE 或 DELETE 审计行,就引发异常。
-- Only the app role may insert; nobody may update or delete
REVOKE ALL ON audit_log FROM PUBLIC;
GRANT INSERT ON audit_log TO app_role;
GRANT SELECT ON audit_log TO auditor_role;
-- Trigger to block any tampering
CREATE OR REPLACE FUNCTION fn_protect_audit()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
RAISE EXCEPTION 'audit_log rows are immutable';
RETURN NULL;
END;
$$;
CREATE TRIGGER trg_protect_audit
BEFORE UPDATE OR DELETE ON audit_log
FOR EACH ROW EXECUTE FUNCTION fn_protect_audit();审计 RLS 策略决策
启用行级安全性后,PostgreSQL 会静默隐藏行,而不是引发错误。因此,您很难知道用户是否尝试读取自己无权查看的行。一种方法是添加一个宽松策略,在限制性策略过滤行之前始终插入审计记录。
下面的查询展示了如何检查某个表上存在哪些 RLS 策略,以及这些策略适用于哪些角色。
-- View all RLS policies on the patients table
SELECT polname AS policy_name,
polcmd AS command,
polroles::TEXT AS applies_to,
polqual::TEXT AS using_expression,
polwithcheck::TEXT AS with_check_expression
FROM pg_policy
WHERE polrelid = 'patients'::REGCLASS
ORDER BY polname;知识检查
请测试您对 SQL 中访问审计的理解。
课程回顾
在本课程中,您学习了如何在 PostgreSQL 中构建完整的访问审计系统:
- 审计日志表 — 一个基于 JSONB 的表,用于记录谁在何时执行了什么操作。
- 触发器函数 — 使用
TG_TABLE_NAME、row_to_json()和current_user,自动记录任何关联表的 INSERT、UPDATE 和 DELETE 事件。 - 读取审计 — 将敏感表封装在函数中,在返回数据前记录 SELECT 事件。
- 内置监控 —
pg_stat_activity显示实时会话;服务器端日志记录无需修改代码即可捕获查询。 - 异常检测 — 对审计日志执行聚合查询可以标记异常的访问量。
- 不可变性 — 撤销审计表上的 UPDATE/DELETE 权限,并添加阻止性触发器以防篡改。
设计良好的审计轨迹是回答谁访问了什么这一问题最可靠的工具,也是任何合规性或安全调查的基础。
常见问题解答
「审计访问权限」课时是免费的吗?
是的 — 「审计访问权限」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Academy 课程的其余内容,请升级到 CoddyKit PRO。 SQL Academy 课程共包含 4 节课。
「审计访问权限」这节课中我会学到什么?
追踪谁可以查看哪些内容 你通过在浏览器中直接运行的动手代码来练习 SQL Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 SQL Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 SQL Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「审计访问权限」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 SQL Academy 课中编写并运行代码吗?
能。每节 SQL Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。