代码编织梦想

目录结构

注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:

1、参考书籍:《PostgreSQL数据库内核分析》
2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》
3、PostgreSQL数据库仓库链接,点击前往
4、日本著名PostgreSQL数据库专家 铃木启修 网站主页,点击前往
5、参考书籍:《PostgreSQL中文手册》
6、参考书籍:《PostgreSQL指南:内幕探索》,点击前往
7、参考书籍:《事务处理 概念与技术》
8、PostgreSQL数据库仓库链接,点击前往
9、PostgreSQL中文社区,点击前往
10、PostgreSQL数据库官方文档,点击前往


1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 💖)
5、本文内容基于master源码开发而成


PostgreSQL数据库 SYSTEM_USER reserved word implementation 实现原理说明



文章快速说明索引

学习目标:

做数据库内核开发久了就会有一种 少年得志,年少轻狂 的错觉,然鹅细细一品觉得自己其实不算特别优秀 远远没有达到自己想要的。也许光鲜的表面掩盖了空洞的内在,每每想到于此,皆有夜半临渊如履薄冰之感。为了睡上几个踏实觉,即日起 暂缓其他基于PostgreSQL数据库的兼容功能开发,近段时间 将着重于学习分享Postgres的基础知识和实践内幕。


学习内容:(详见目录)

1、PostgreSQL数据库 SYSTEM_USER reserved word implementation 实现原理说明


学习时间:

2022年09月29日 21:30:08


学习产出:

1、PostgreSQL数据库基础知识回顾 1个
2、CSDN 技术博客 1篇
3、PostgreSQL数据库内核深入学习


注:下面我们所有的学习环境是Ubuntu 22.04+PostgreSQL 16devel+Oracle11g+MySQL8.0

postgres=# select version();
                                               version                                               
-----------------------------------------------------------------------------------------------------
 PostgreSQL 16devel on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0, 64-bit
(1 row)

postgres=#

#-----------------------------------------------------------------------------#

SQL> select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
PL/SQL Release 11.2.0.1.0 - Production
CORE	11.2.0.1.0	Production
TNS for Linux: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 - Production

SQL>

#-----------------------------------------------------------------------------#

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.27    |
+-----------+
1 row in set (0.06 sec)

mysql>

问题描述背景说明

在开始之前,先来看一下今天我们学习的背景:

  1. 这个是发往邮件列表pgsql-hackers的一项提议
  2. 时间:2022-06-22 13:25:22
  3. 分支:master 已合入
  4. commitid:0823d061b0b7f1e20fbfd48bef3c2e093493dbd4,点击前往
  5. 提议者:Drouvot, Bertrand
  6. 主题:SYSTEM_USER reserved word implementation

详细如下(作者自述):

The SYSTEM_USER is a sql reserved word as mentioned in [1] and is currently not implemented.
-- SYSTEM_USER 是 [1] 中提到的 sql 保留字,目前未实现

Please find attached a patch proposal to make use of the SYSTEM_USER so that it returns the authenticated identity (if any) (aka authn_id in the Port struct).
-- 请找到附加的补丁提案以使用 SYSTEM_USER 以便它返回经过身份验证的身份(如果有)(也称为 Port 结构中的 authn_id)

Indeed in some circumstances, the authenticated identity is not the SESSION_USER and then the information is lost from the connection point of view (it could still be retrieved thanks to commit 9afffcb833 and log_connections set to on).
-- 实际上,在某些情况下,经过身份验证的身份不是 SESSION_USER,然后从连接的角度来看,信息会丢失(由于提交 9afffcb833 和 log_connections 设置为 on,它仍然可以检索)
_Example 1, using the gss authentification._
-- 示例 1,使用 gss 认证

Say we have this entry in pg_hba.conf:
-- 假设我们在 pg_hba.conf 中有这个条目:

host all all 0.0.0.0/0 gss map=mygssmap

and the related mapping in pg_ident.conf
-- 以及 pg_ident.conf 中的相关映射

mygssmap   /^(.*@.*)\.LOCAL$    mary

Then, connecting with a valid Kerberos Ticket that contains “bertrand@BDTFOREST.LOCALas the default principal that way:
-- 然后,使用包含“bertrand@BDTFOREST.LOCAL”作为默认主体的有效 Kerberos 票证进行连接:

psql -U mary -h myhostname -d postgres -- 我们将得到:

postgres=> select current_user, session_user;
  current_user | session_user
--------------+--------------
  mary         | mary
(1 row)

While the SYSTEM_USER would produce the Kerberos principal:
-- 虽然 SYSTEM_USER 会生成 Kerberos 主体:
postgres=> select system_user;
        system_user
--------------------------
bertrand@BDTFOREST.LOCAL
(1 row)
_Example 2, using the peer authentification._
-- _ 示例 2,使用对等身份验证

Say we have this entry in pg_hba.conf:
-- 假设我们在 pg_hba.conf 中有这个条目:

local all john peer map=mypeermap

and the related mapping in pg_ident.conf:
-- 以及 pg_ident.conf 中的相关映射

mypeermap postgres john

Then connected localy as the system user postgres and connecting to the database that way: psql -U john -d postgres, we will get:
-- 然后以系统用户 postgres 连接本地并以这种方式连接到数据库:psql -U john -d postgres,我们将得到:
postgres=> select current_user, session_user;
  current_user | session_user
--------------+--------------
  john         | john
(1 row)

While the SYSTEM_USER would produce the system user that requested the connection:
-- 虽然 SYSTEM_USER 会生成请求连接的系统用户:
postgres=> select system_user;
  system_user
-------------
  postgres
(1 row)
Thanks to those examples we have seen some situations where the information related to the authenticated identity has been lost from the connection point of view (means not visible in the current_session or in the session_user).
-- 多亏了这些示例,我们已经看到了一些与已验证身份相关的信息从连接的角度丢失的情况(意味着在 current_session 或 session_user 中不可见)

The purpose of this patch is to make it visible through the SYSTEM_USER sql reserved word.
-- 此补丁的目的是通过 SYSTEM_USER sql 保留字使其可见。
Remarks: 
-- 评论:

- In case port->authn_id is NULL then the patch is returning the SESSION_USER for the SYSTEM_USER. Perhaps it should return NULL instead.
-- 如果port->authn_id为NULL,则修补程序将为SYSTEM_USER返回SESSION_USER。也许它应该返回NULL

- There is another thread [2] to expose port->authn_id to extensions and triggers thanks to a new API. This thread [2] leads to discussions about providing this information to the parallel workers too. While the new MyClientConnectionInfo being discussed in [2] could be useful to hold the client information that needs to be shared between the backend and any parallel workers, it does not seem to be needed in the case port->authn_id is exposed through SYSTEM_USER (like it is not for CURRENT_USER and SESSION_USER).
-- 由于有了一个新的API,还有一个线程[2]向扩展和触发器公开port->authn_id
-- 这条线索[2]也引发了关于向并行工作人员提供此信息的讨论
-- 虽然[2]中讨论的新MyClientConnectionInfo可能有助于保存需要在后端和任何并行工作线程之间共享的客户端信息,但在端口->authn_id通过SYSTEM_USER公开的情况下似乎不需要它(就像它不适用于CURRENT_USER和SESSION_USER一样)

I will add this patch to the next commitfest. I look forward to your feedback.
-- 我将把这个补丁添加到下一次提交大会。我期待您的反馈

接下来看一下master分支现在的情况,为了方便查看 把GUC参数log_connections在代码层面改为真,如下:

// src/backend/utils/misc/guc_tables.c

	{
		{"log_connections", PGC_SU_BACKEND, LOGGING_WHAT,
			gettext_noop("Logs each successful connection."),
			NULL
		},
		&Log_connections,
		true, // here
		NULL, NULL, NULL
	},
// 初始化:
initdb: warning: enabling "trust" authentication for local connections
...

[postgres@gpadmin1:~/test/bin]$ ./psql 
2022-09-29 21:17:01.512 CST [21253] LOG:  connection received: host=[local]
2022-09-29 21:17:01.513 CST [21253] LOG:  connection authorized: user=postgres database=postgres application_name=psql
psql (16devel)
Type "help" for help.

postgres=# 
\q
[postgres@gpadmin1:~/test/bin]$
[postgres@gpadmin1:~/test/bin]$ vim test/pg_hba.conf
...

[postgres@gpadmin1:~/test/bin]$ ./psql 
2022-09-29 21:22:59.786 CST [21859] LOG:  connection received: host=[local]
Password for user postgres: 
2022-09-29 21:23:01.912 CST [21875] LOG:  connection received: host=[local]
2022-09-29 21:23:01.929 CST [21875] LOG:  connection authenticated: identity="postgres" method=md5 (/home/postgres/test/bin/test/pg_hba.conf:89)
2022-09-29 21:23:01.929 CST [21875] LOG:  connection authorized: user=postgres database=postgres application_name=psql
psql (16devel)
Type "help" for help.

postgres=# 
\q
[postgres@gpadmin1:~/test/bin]$
// 上面的改动如下所示:

在这里插入图片描述


过了一夜时间,该功能已合入master分支(现在 2022-09-30 13:36:50),提交信息如下:

在这里插入图片描述

-- SYSTEM_USER是SQL规范中的一个保留关键字,大致描述一下,它旨在报告有关连接到数据库服务器的系统用户的一些信息
-- 它可能包括有关用户连接方式的特定于实现的信息,如身份验证方法

-- 此提交将SYSTEM_USER实现为auth_method:identity
-- 其中“auth_method”是用于登录服务器的身份验证方法的关键字(如peer、md5、scram-sha-256、gss等)
-- “identity”是9afffcb引入的身份验证标识(peer将authn设置为OS用户名,gss设置为用户主体,等等)
-- 这种格式是由Tom Lane建议的

-- 请注意,由于d951052,SYSTEM_USER可供并行worker使用

建立连接过程调试

代码逻辑

匹配规则,如下:

local   all             all                                     md5
[postgres@postgres-virtual-machine:~/test/bin]$ ./psql 
2022-09-30 13:04:52.347 CST [12438] LOG:  connection received: host=[local]
Password for user postgres: 
2022-09-30 13:04:53.847 CST [12439] LOG:  connection received: host=[local]
2022-09-30 13:04:53.864 CST [12439] LOG:  connection authenticated: identity="postgres" method=md5 (/home/postgres/test/bin/test/pg_hba.conf:89)
2022-09-30 13:04:53.864 CST [12439] LOG:  connection authorized: user=postgres database=postgres application_name=psql
psql (16devel)
Type "help" for help.

postgres=#

如上,总共有4条的 日志打印,下面我们先演示一下:

在这里插入图片描述

// src/interfaces/libpq/fe-connect.c

/*
 *		PQconnectdbParams
 *
 * establishes a connection to a postgres backend through the postmaster
 * using connection information in two arrays.
 * 使用两个数组中的连接信息,通过postmaster建立到postgres后端的连接
 *
 * The keywords array is defined as
 *
 *	   const char *params[] = {"option1", "option2", NULL}
 *
 * The values array is defined as
 *
 *	   const char *values[] = {"value1", "value2", NULL}
 *
 * Returns a PGconn* which is needed for all subsequent libpq calls, or NULL
 * if a memory allocation failed.
 * If the status field of the connection returned is CONNECTION_BAD,
 * then some fields may be null'ed out instead of having valid values.
 * 返回所有后续libpq调用所需的PGconn*,如果内存分配失败,则返回NULL
 * 如果返回的连接的状态字段为connection_BAD,则某些字段可能为空,而不是具有有效值
 *
 * You should call PQfinish (if conn is not NULL) regardless of whether this
 * call succeeded.
 * 无论此调用是否成功,都应该调用PQfinish(如果conn不为NULL)
 */
PGconn *
PQconnectdbParams(const char *const *keywords,
				  const char *const *values,
				  int expand_dbname)
{
	PGconn	   *conn = PQconnectStartParams(keywords, values, expand_dbname);

	if (conn && conn->status != CONNECTION_BAD)
		(void) connectDBComplete(conn);

	return conn;
}

我们先看一下 服务端的逻辑:

static int
ServerLoop(void)
{
	...
	for (;;)
	{
		// 下面省略诸多细节
		selres = select(nSockets, &rmask, NULL, NULL, &timeout);
		...
		/*
		 * New connection pending on any of our sockets? If so, fork a child
		 * process to deal with it.
		 * 是否有任何套接字上的新连接挂起?如果是这样,则派生一个子进程来处理它
		 */
		if (selres > 0)
		{
			...
			BackendStartup(port);
			...
		}
		...
	}
	...
}
/*
 * BackendStartup -- start backend process
 *
 * returns: STATUS_ERROR if the fork failed, STATUS_OK otherwise.
 * 如果fork失败,则返回:STATUS_ERROR,否则返回STATUS_OK
 *
 * Note: if you change this code, also consider StartAutovacuumWorker.
 * 注意:如果更改此代码,还应考虑StartAutovacuumWorker
 */
static int
BackendStartup(Port *port)
{
	...
	pid = fork_process();
	if (pid == 0)				/* child */
	{
		...

		/* Perform additional initialization and collect startup packet */
		/* 执行附加初始化并收集启动数据包 */
		BackendInitialize(port);

		/*
		 * Create a per-backend PGPROC struct in shared memory. We must do
		 * this before we can use LWLocks. In the !EXEC_BACKEND case (here)
		 * this could be delayed a bit further, but EXEC_BACKEND needs to do
		 * stuff with LWLocks before PostgresMain(), so we do it here as well
		 * for symmetry.
		 *  
		 * 在共享内存中创建每后端PGPROC结构。我们必须这样做,然后才能使用LWLocks
		 * 在里面!EXEC_BACKEND案例(此处)这可能会延迟一点,但EXEC_巴CKEND需要在PostgresMain()之前使用LWLocks
		 * 因此我们在此处也这样做是为了对称
		 */
		InitProcess();

		/* And run the backend */
		/* 并运行后端 */
		BackendRun(port);
	}
	...
}

继续细化,如下:

/*
 * BackendInitialize -- initialize an interactive (postmaster-child) 初始化交互式
 *				backend process, and collect the client's startup packet.
 *				后端进程,并收集客户端的启动数据包
 *
 * returns: nothing.  Will not return at all if there's any failure.
 * 返回:无。如果出现任何故障,将根本不返回
 *
 * Note: this code does not depend on having any access to shared memory.
 * Indeed, our approach to SIGTERM/timeout handling *requires* that
 * shared memory not have been touched yet; see comments within.
 * In the EXEC_BACKEND case, we are physically attached to shared memory
 * but have not yet set up most of our local pointers to shmem structures.
 *  
 * 注意:此代码不依赖于是否可以访问共享内存
 * 事实上,我们的SIGTERM/timeout处理方法*要求*尚未触及共享内存;请参阅中的注释
 * 在EXEC_BACKEND情况下,我们物理上连接到共享内存,但尚未设置大多数指向shmem结构的本地指针
 */
static void
BackendInitialize(Port *port)
{
	...
	/* And now we can issue the Log_connections message, if wanted */
	if (Log_connections)
	{
		if (remote_port[0])
			ereport(LOG,
					(errmsg("connection received: host=%s port=%s",
							remote_host,
							remote_port)));
		else
			ereport(LOG,
					(errmsg("connection received: host=%s",
							remote_host)));
	}
	...
}
/*
 * BackendRun -- set up the backend's argument list and invoke PostgresMain()
 * 				 设置后端的参数列表并调用PostgresMain()
 *
 * returns:
 *		Doesn't return at all.
 */
static void
BackendRun(Port *port)
{
	/*
	 * Make sure we aren't in PostmasterContext anymore.  (We can't delete it
	 * just yet, though, because InitPostgres will need the HBA data.)
	 */
	MemoryContextSwitchTo(TopMemoryContext);

	PostgresMain(port->database_name, port->user_name);
}

/* ----------------------------------------------------------------
 * PostgresMain
 *	   postgres main loop -- all backends, interactive or otherwise loop here
 *	   postgres主循环——所有后端、交互式或其他循环在此
 *
 * dbname is the name of the database to connect to, username is the
 * PostgreSQL user name to be used for the session.
 * dbname是要连接的数据库的名称,username是会话使用的PostgreSQL用户名
 *
 * NB: Single user mode specific setup should go to PostgresSingleUserMain()
 * if reasonably possible.
 * 注意:如果合理可行,单用户模式特定设置应转到PostgresSingleUserMain()
 * ----------------------------------------------------------------
 */
void
PostgresMain(const char *dbname, const char *username)
{
	...
	/*
	 * General initialization.
	 * 常规初始化
	 *
	 * NOTE: if you are tempted to add code in this vicinity, consider putting
	 * it inside InitPostgres() instead.  In particular, anything that
	 * involves database access should be there, not here.
	 * 注意:如果您想在此附近添加代码,请考虑将其放在InitPostgres()中
	 * 特别是,任何涉及数据库访问的内容都应该在那里,而不是在这里
	 */
	InitPostgres(dbname, InvalidOid,	/* database to connect to */
				 username, InvalidOid,	/* role to connect as */
				 !am_walsender, /* honor session_preload_libraries? */
				 false,			/* don't ignore datallowconn */
				 NULL);			/* no out_dbname */
	...
	/*
	 * Non-error queries loop here.
	 * 无错误查询在此循环
	 */

	for (;;)
	{
		...
	}
	...
}
void
InitPostgres(const char *in_dbname, Oid dboid,
			 const char *username, Oid useroid,
			 bool load_session_libraries,
			 bool override_allow_connections,
			 char *out_dbname)
{
	...
	else
	{
		/* normal multiuser case */
		Assert(MyProcPort != NULL);
		PerformAuthentication(MyProcPort);
		InitializeSessionUserId(username, useroid);
		/* ensure that auth_method is actually valid, aka authn_id is not NULL */
		if (MyClientConnectionInfo.authn_id)
			InitializeSystemUser(MyClientConnectionInfo.authn_id,
								 hba_authname(MyClientConnectionInfo.auth_method));
		am_superuser = superuser();
	}
	...
}

接下来就着重看一下PerformAuthentication函数,如下:

/*
 * PerformAuthentication -- authenticate a remote client 对远程客户端进行身份验证
 *
 * returns: nothing.  Will not return at all if there's any failure.
 * 返回:无。如果出现任何故障,将根本不返回
 */
static void
PerformAuthentication(Port *port)
{
	...
	/*
	 * Now perform authentication exchange.
	 * 现在执行身份验证交换
	 */
	set_ps_display("authentication");
	ClientAuthentication(port); /* might not return, if failure */ /* 如果失败,可能不会返回 */

	/*
	 * Done with authentication.  Disable the timeout, and log if needed.
	 * 已完成身份验证。禁用超时,并根据需要记录
	 */
	disable_timeout(STATEMENT_TIMEOUT, false);

	if (Log_connections)
	{
		StringInfoData logmsg;

		initStringInfo(&logmsg);
		if (am_walsender)
			appendStringInfo(&logmsg, _("replication connection authorized: user=%s"),
							 port->user_name);
		else
			appendStringInfo(&logmsg, _("connection authorized: user=%s"),
							 port->user_name);
		if (!am_walsender)
			appendStringInfo(&logmsg, _(" database=%s"), port->database_name);

		if (port->application_name != NULL)
			appendStringInfo(&logmsg, _(" application_name=%s"),
							 port->application_name);

		...

		ereport(LOG, errmsg_internal("%s", logmsg.data));
		pfree(logmsg.data);
	}
	...
}

至此,将会来到身份验证的核心中的核心 ClientAuthentication函数,如下:

/*
 * Client authentication starts here.  If there is an error, this
 * function does not return and the backend process is terminated.
 * 客户端身份验证从这里开始。如果出现错误,此函数将不返回,后端进程将终止
 */
void
ClientAuthentication(Port *port)
{
	...
	...
}

代码调试

注1:遇到Ubuntu下vscode使用gdb调试的时候,出现权限的问题 设置如下:

 sudo sysctl -w kernel.yama.ptrace_scope=0

注2:使用vscode调试 PostgreSQL数据库多进程环境(附加子进程),如下是调试 psql建立连接的后端逻辑:

第一点:配置

		{
            "name": "(gdb) 附加",
            "type": "cppdbg",
            "request": "attach",
            "program": "/home/postgres/test/bin/postgres",
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description":  "将反汇编风格设置为 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                },
                {
                    "text": "-gdb-set follow-fork-mode child" // here
                }
            ]
        }

第二点:附加进程 服务进程
第三点:断点 BackendStartup


我们这里描述一下这个过程,客户端进行了两次连接数据库(注意:这是md5的认证方式):

  • 第一次:无密码 肯定失败
  • 第二次:密码输入 “1”,成功

第一次,进入CheckPWChallengeAuth

	/* First look up the user's password. */
	shadow_pass = get_role_password(port->user_name, logdetail);

在这里插入图片描述

postgres=# \x
Expanded display is on.
postgres=# select * from pg_authid where rolname = 'postgres';
-[ RECORD 1 ]--+--------------------------------------------------------------------------------------------------------------------------------------
oid            | 10
rolname        | postgres
rolsuper       | t
rolinherit     | t
rolcreaterole  | t
rolcreatedb    | t
rolcanlogin    | t
rolreplication | t
rolbypassrls   | t
rolconnlimit   | -1
rolpassword    | SCRAM-SHA-256$4096:v/aOc4qoJxrJSWMQsWtViA==$j45I+yWNPzwWmFTiDD/SaQ6mSN6jggLSOagFoNJDJec=:OnNZ1vxpdEr4rERxkga3JZNkN8+FEZGUm7Ww4EwPzr4=
rolvaliduntil  | 

postgres=# \x
Expanded display is off.
postgres=#

然后通过之后的get_password_type函数得到shadow_pass的类型是:PASSWORD_TYPE_SCRAM_SHA_256

	/*
	 * If 'md5' authentication is allowed, decide whether to perform 'md5' or
	 * 'scram-sha-256' authentication based on the type of password the user
	 * has.  If it's an MD5 hash, we must do MD5 authentication, and if it's a
	 * SCRAM secret, we must do SCRAM authentication.
	 * 如果允许“md5”身份验证,请根据用户的密码类型决定是执行“md5)”身份验证还是执行“scram-sha-256”身份验证
	 * 如果是MD5散列,我们必须进行MD5身份验证;如果是SCRAM机密,我们必须执行SCRAM身份验证
	 *
	 * If MD5 authentication is not allowed, always use SCRAM.  If the user
	 * had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will
	 * fail.
	 * 如果不允许MD5身份验证,请始终使用SCRAM。如果用户有MD5密码,则使用SCRAM机制检查SASLAuth()将失败
	 */
	if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5)
		auth_result = CheckMD5Auth(port, shadow_pass, logdetail);
	else
		auth_result = CheckSASLAuth(&pg_be_scram_mech, port, shadow_pass,
									logdetail); // here

在这里插入图片描述

于是在上面的CheckSASLAuth函数返回的是STATUS_EOF,之后就导致auth_failed 子进程退出:

在这里插入图片描述

子进程退出,那么就使得:

ClientAuthentication(port); /* might not return, if failure */

第二次:因为第二次是要求输入了密码,所以是可以的!但是由于第一次的影响,调试无法进行 所以这里我们稍微修改一下代码:

  • 将客户端的 passwd 写死,为 “1”

在这里插入图片描述

这样就方便我们调试有密码的认证过程,如下:

在这里插入图片描述

注:关于密码验证的函数CheckSASLAuth 我们这里不再叙述,原因 如下:

  • 太过复杂
  • 跟我们今天学习的内容 关联不大

这里CheckSASLAuth函数返回值为:STATUS_OK,接下来如下:

	if (auth_result == STATUS_OK)
		set_authn_id(port, port->user_name);

/*
 * Sets the authenticated identity for the current user.  The provided string
 * will be stored into MyClientConnectionInfo, alongside the current HBA
 * method in use.  The ID will be logged if log_connections is enabled.
 * 设置当前用户的已验证身份。提供的字符串将与当前使用的HBA方法一起存储到MyClientConnectionInfo中
 * 如果启用了log_connections,则会记录ID
 *
 * Auth methods should call this routine exactly once, as soon as the user is
 * successfully authenticated, even if they have reasons to know that
 * authorization will fail later.
 * 一旦用户成功通过身份验证,身份验证方法应该准确地调用此例程一次,即使它们有理由知道稍后的授权将失败
 *
 * The provided string will be copied into TopMemoryContext, to match the
 * lifetime of MyClientConnectionInfo, so it is safe to pass a string that is
 * managed by an external library.
 * 提供的字符串将复制到TopMemoryContext中,以匹配MyClientConnectionInfo的生存期,因此传递由外部库管理的字符串是安全的
 */
static void
set_authn_id(Port *port, const char *id)
{
	Assert(id);

	if (MyClientConnectionInfo.authn_id)
	{
		/*
		 * An existing authn_id should never be overwritten; that means two
		 * authentication providers are fighting (or one is fighting itself).
		 * Don't leak any authn details to the client, but don't let the
		 * connection continue, either.
		 */
		ereport(FATAL,
				(errmsg("authentication identifier set more than once"),
				 errdetail_log("previous identifier: \"%s\"; new identifier: \"%s\"",
							   MyClientConnectionInfo.authn_id, id)));
	}

	MyClientConnectionInfo.authn_id = MemoryContextStrdup(TopMemoryContext, id);
	MyClientConnectionInfo.auth_method = port->hba->auth_method;

	if (Log_connections)
	{
		ereport(LOG,
				errmsg("connection authenticated: identity=\"%s\" method=%s "
					   "(%s:%d)",
					   MyClientConnectionInfo.authn_id,
					   hba_authname(MyClientConnectionInfo.auth_method),
					   HbaFileName, port->hba->linenumber));
	}
}

如上,这里就会打印connection authenticated,但是这个函数最重要的是:

	MyClientConnectionInfo.authn_id = MemoryContextStrdup(TopMemoryContext, id);
	MyClientConnectionInfo.auth_method = port->hba->auth_method;

这是此次功能SYSTEM_USER实现的基础,我们后面会详述!

之后的处理,如下:

CheckPWChallengeAuth
	|
ClientAuthentication

在这里插入图片描述

至此,这四行日志的打印 逻辑处理已讲解完毕!


system_user 功能

功能演示非常简单,如下:

[postgres@postgres-virtual-machine:~/test/bin]$ ./psql 
2022-09-30 16:32:37.687 CST [23786] LOG:  connection received: host=[local]
2022-09-30 16:32:37.702 CST [23786] LOG:  connection authenticated: identity="postgres" method=md5 (/home/postgres/test/bin/test/pg_hba.conf:89)
2022-09-30 16:32:37.702 CST [23786] LOG:  connection authorized: user=postgres database=postgres application_name=psql
psql (16devel)
Type "help" for help.

postgres=# select current_user,session_user,system_user;
 current_user | session_user | system_user  
--------------+--------------+--------------
 postgres     | postgres     | md5:postgres
(1 row)

postgres=# \df+ system_user
                                                                                   List of functions
   Schema   |    Name     | Result data type | Argument data types | Type | Volatility | Parallel |  Owner   | Security | Access privileges | Language | Source code |   Description    
------------+-------------+------------------+---------------------+------+------------+----------+----------+----------+-------------------+----------+-------------+------------------
 pg_catalog | system_user | text             |                     | func | stable     | safe     | postgres | invoker  |                   | internal | system_user | system user name
(1 row)

postgres=#

至于新增的关键字 语法等不再赘述:

在这里插入图片描述


相关函数 如下:

/*
 * Return the system user representing the authenticated identity.
 * It is defined in InitializeSystemUser() as auth_method:authn_id.
 */
const char *
GetSystemUser(void)
{
	return SystemUser;
}

/*
 * SQL-function SYSTEM_USER
 */
Datum
system_user(PG_FUNCTION_ARGS)
{
	const char *sysuser = GetSystemUser();

	if (sysuser)
		PG_RETURN_DATUM(CStringGetTextDatum(sysuser));
	else
		PG_RETURN_NULL();
}

接下来就看一下给全局变量SystemUser初始化的函数InitializeSystemUser,如下:

// 

/*
 * Initialize the system user.
 *
 * This is built as auth_method:authn_id.
 */
void
InitializeSystemUser(const char *authn_id, const char *auth_method)
{
	char	   *system_user;

	/* call only once */
	Assert(SystemUser == NULL);

	/*
	 * InitializeSystemUser should be called only when authn_id is not NULL,
	 * meaning that auth_method is valid.
	 *  
	 * 仅当authn_id不为NULL时才应调用InitializeSystemUser,这意味着auth_method有效
	 */
	Assert(authn_id != NULL);

	system_user = psprintf("%s:%s", auth_method, authn_id);

	/* Store SystemUser in long-lived storage */
	SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
	pfree(system_user);
}

我们这里不谈并行相关的逻辑,下面来看一下InitializeSystemUser这个初始化函数的调用点。接着我们上面建立连接的过程:

// PerformAuthentication 客户端认证的入口


	else
	{
		/* normal multiuser case */
		Assert(MyProcPort != NULL);
		PerformAuthentication(MyProcPort); // here
		InitializeSessionUserId(username, useroid);
		/* ensure that auth_method is actually valid, aka authn_id is not NULL */
		if (MyClientConnectionInfo.authn_id)
			InitializeSystemUser(MyClientConnectionInfo.authn_id,
								 hba_authname(MyClientConnectionInfo.auth_method));
		am_superuser = superuser();
	}

在这里插入图片描述

OK,看一下这段逻辑并回头看一下函数set_authn_id的处理,至此 整个代码梳理结束!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43949535/article/details/127114372