由 John Doe 五月 20, 2025
你想要对 PostgreSQL 的用户登录事件进行处理吗?现在,PostgreSQL 已经支持登录验证的事件触发器了。
特性提交日志
支持登录验证的事件触发器。
此提交引入了登录事件触发器,允许在用户连接时立即触发某些操作。这对日志记录、连接检查以及环境个性化设置非常有用。具体用法在官方的文档中描述,但简而言之,其用法与其他触发器相同:创建返回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