PostgreSQL 17: 登录验证的事件触发器

John Doe 五月 20, 2025

你想要对 PostgreSQL 的用户登录事件进行处理吗?现在,PostgreSQL 已经支持登录验证的事件触发器了。

与 SQL 共舞的大象

特性提交日志

支持登录验证的事件触发器。

此提交引入了登录事件触发器,允许在用户连接时立即触发某些操作。这对日志记录、连接检查以及环境个性化设置非常有用。具体用法在官方的文档中描述,但简而言之,其用法与其他触发器相同:创建返回event_trigger类型的函数,然后在登录事件上创建事件触发器。

为了避免在没有触发器时产生连接时间开销,该提交引入了pg_database.datashasloginevt标志,用于指示数据库是否存在活动的登录触发器。该标志通过CREATE/ALTER EVENT TRIGGER命令设置,并在连接时未发现活动触发器时取消设置。

讨论:https://postgr.es/m/0d46d29f-4558-3af9-9c85-7774e14a7709%40postgrespro.ru

示例

很久以前我们就有了事件触发器。它们在一些场景中确实很有用,PostgreSQL 社区中有一些工具,如 pgl_ddl_deploy,大量地依赖事件触发器。

现在,我们可以在每个会话创建时执行一些代码。示例如下:

CREATE OR replace function login_validate()
 RETURNS event_trigger language plpgsql as $$
DECLARE
BEGIN
    IF session_user <> 'redrock' THEN
        raise exception 'Go AWAY';
    end if;
    raise log 'user login: %', session_user;
END;
$$;

CREATE EVENT TRIGGER login_validate
  ON login
  EXECUTE FUNCTION login_validate();

如果我尝试使用 redrock 账户登录数据库,并执行查询:

psql -U redrock -d redrock -X -c 'select now()'
              now
-------------------------------
 2025-04-24 14:49:58.931324+08
(1 row)

在 PostgreSQL 日志中会看到:

2025-04-24 14:49:58.929 CST redrock@redrock 403707 [local] LOG:  connection authenticated: user="redrock" method=trust (/home/postgres/data/pg_hba.conf:119)
2025-04-24 14:49:58.929 CST redrock@redrock 403707 [local] LOG:  connection authorized: user=redrock database=redrock application_name=psql
2025-04-24 14:49:58.931 CST redrock@redrock 403707 [local] LOG:  user login: redrock
2025-04-24 14:49:58.931 CST redrock@redrock 403707 [local] CONTEXT:  PL/pgSQL function login_validate() line 7 at RAISE
2025-04-24 14:49:58.931 CST redrock@redrock 403707 [local] LOG:  duration: 0.140 ms  statement: select now()
2025-04-24 14:49:58.931 CST redrock@redrock 403707 [local] LOG:  disconnection: session time: 0:00:00.002 user=redrock database=redrock host=[local]

但如果其他用户尝试执行相同操作:

psql -U test -d redrock -X -c 'select now()'
psql: error: connection to server on socket "/tmp/.s.PGSQL.5430" failed: FATAL:  Go away
CONTEXT:  PL/pgSQL function login_validate() line 5 at RAISE

日志中会记录:

2025-04-24 14:50:46.689 CST test@redrock 404179 [local] LOG:  connection authenticated: user="test" method=trust (/home/postgres/data/pg_hba.conf:119)
2025-04-24 14:50:46.689 CST test@redrock 404179 [local] LOG:  connection authorized: user=test database=redrock application_name=psql
2025-04-24 14:50:46.690 CST test@redrock 404179 [local] FATAL:  Go away
2025-04-24 14:50:46.690 CST test@redrock 404179 [local] CONTEXT:  PL/pgSQL function login_validate() line 5 at RAISE
2025-04-24 14:50:46.690 CST test@redrock 404179 [local] LOG:  disconnection: session time: 0:00:00.002 user=test database=redrock host=[local]

当然,禁止用户连接有更简单的方法,但这种方式有助于实施特定的策略,例如:要求每个连接必须设置application_name

CREATE OR replace function login_validate()
 RETURNS event_trigger language plpgsql as $$
DECLARE
BEGIN
    IF '' = current_setting('application_name') THEN
        raise exception 'You don''t have application_name set!';
    end if;
    raise log 'user logged with application name: %', current_setting('application_name');
END;
$$;

CREATE EVENT TRIGGER login_validate
  ON login
  EXECUTE FUNCTION login_validate();

我仍可以使用psql正常连接:

psql -X -c 'select now()'
              now
-------------------------------
 2025-04-24 14:54:11.250606+08
(1 row)

但如果尝试运行未设置application_name的简单应用,如下面的 Perl 脚本:

perl -le 'use DBI; print DBI->connect("dbi:Pg:dbname=redrock;host=127.0.0.1;port=5430")->selectall_arrayref("select now()")->[0]->[0]'
DBI connect('dbname=redrock;host=127.0.0.1;port=5430','',...) failed: connection to server at "127.0.0.1", port 5430 failed: FATAL:  You don't have application_name set!
CONTEXT:  PL/pgSQL function login_validate() line 5 at RAISE at -e line 1.
Can't call method "selectall_arrayref" on an undefined value at -e line 1.

如果通过环境变量或连接选项提供application_name,则可以正常连接:

PGAPPNAME=test perl -le 'use DBI; print DBI->connect("dbi:Pg:dbname=redrock;host=127.0.0.1;port=5430")->selectall_arrayref("select now()")->[0]->[0]'
2025-04-24 14:57:10.602237+08

perl -le 'use DBI; print DBI->connect("dbi:Pg:dbname=redrock;host=127.0.0.1;port=5430;application_name=whatever")->selectall_arrayref("select now()")->[0]->[0]'
2025-04-24 15:15:03.451867+08

使用相同的方法,我们也可以创建触发器来禁止特定用户在特定时间登录,或在登录时为用户预设置某些全局参数。

非常不错的特性,感谢所有参与的社区人员。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/e83d1b0c40ccda8955f1245087f0697652c4df86