0Pricing
SQL Academy · 课时

审计访问权限

追踪谁可以查看哪些内容

审计访问权限 是 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 角色与权限
  2. 行级安全策略
  3. 列级权限
  4. 审计访问权限
← 返回 SQL Academy