PostgreSQL学习笔记----对象访问控制

在 PostgreSQL中,建立安全的数据库连接是由用户标识和认证技术来共同实现,而建立连接后的安全访问保护则是基于角色的对象访问控制。

数据库里的每个对象所拥有的权限信息经常会发生变化,比如授予对象的部分操作权限给其他用户,或者删除用户在对象上的操作权限,亦或是对用户在对象上的操作权限进行更新等。以上操作在 SQL 中体现为 GRANT 和 REVOKE 语句,且都涉及对象权限信息的动态管理,即权限控制中的对象权限管理。

为了保护数据安全,当用户要对某个数据库对象进行操作之前,必须检查用户在对象上的操作权限,仅当用户对此对象拥有进行合法操作的权限时,才允许用户对此对象执行相应操作。上述操作检查的过程被称为对象权限检查。

ACL

访问控制列表(Access Control List,ACL)是对象权限管理和权限检查的基础,PostgreSQL 通过操作 ACL 实现对象的访问控制管理,在 PostgreSQL 中每个数据库对象都具有 ACL ,每个对象的 ACL 存储了此对象的所有授权信息。当用户访问对象时,只有它在对象的 ACL 中并且具有所需的权限时才能访问该对象。当用户要更新对象的权限时,只需要更新 ACL 上的权限信息即可。

ACL 是存储控制项(Access Control Entruy,ACE)的集合,组织结构如图(取自《PostgreSQL 数据库内核分析》):
在这里插入图片描述
每个 ACL 实际是一个由多个 AclItem 构成的链表。每个 AclItem 对应一个 ACE 。 ACE 中记录着可访问对象的用户或者执行单元,此外还记录了可在对象上进行权限操作的用户或者执行单元。PostgreSQL 中,ACE 由受权者、授权者以及权限位三部分组成。

/*
 * AclItem
 *
 * Note: must be same size on all platforms, because the size is hardcoded
 * in the pg_type.h entry for aclitem.
 */
typedef struct AclItem
{
	Oid			ai_grantee;		/* ID that this item grants privs to */
	Oid			ai_grantor;		/* grantor of privs */
	AclMode		ai_privs;		/* privilege bits */
} AclItem;

其中,字段 ai_privs 是 AclMode 类型。AclMode 是一个 32 位的比特位,其高 16 位为权限选项位,低 16 位为该 ACE 中的操作位权限。每个操作权限占 1 个比特位,当该比特位的取值为 1 时,表示 ACE 中的 ai_grabtee 对应的用户(受权者)具有此对象的相应操作权限,否则,表示用户没有相应权限。AclMode 结构如下所示(取自《PostgreSQL 数据库内核分析》):
在这里插入图片描述
ace type:记录了 ACE 的被授权者(受权者)类型,可以是用户、组或者 public。
Grant options:记录了各权限位对应的授出或者被转授选项。
低 16 位:分别记录了各个权限位的授予情况,从低到高依次为 INSERT、SELECT、UPDATE、DELETE、TRUNCATE 等等。若当授予语句使用 ALL 时,则表示包含所有权限。

ACL 检查

调用 src/backend/catalog/aclchk.c 下的函数 pg_class_aclcheck ,获取用户在该表上的权限集,比较该权限集与操作所需权限集,若前者大于后者,则检查通过。

首先获取到对应的元组,从 pg_class 系统表中:

	/*
	 * Must get the relation's tuple from pg_class
	 */
	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(table_oid));
	if (!HeapTupleIsValid(tuple))
	{
		if (is_missing != NULL)
		{
			/* return "no privileges" instead of throwing an error */
			*is_missing = true;
			return 0;
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_TABLE),
					 errmsg("relation with OID %u does not exist",
							table_oid)));
	}

	classForm = (Form_pg_class) GETSTRUCT(tuple);

判断操作的表是否是系统表,如果是系统表则禁止操作。通过 IsSystemClass 函数判断其 relid 是否小于 FirstUnpinnedObjectId。

/*
	 * Deny anyone permission to update a system catalog unless
	 * pg_authid.rolsuper is set.
	 *
	 * As of 7.4 we have some updatable system views; those shouldn't be
	 * protected in this way.  Assume the view rules can take care of
	 * themselves.  ACL_USAGE is if we ever have system sequences.
	 */
	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
		IsSystemClass(table_oid, classForm) &&
		classForm->relkind != RELKIND_VIEW &&
		!superuser_arg(roleid))
		mask &= ~(ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE);

从 pg_class 系统表中取出操作表的 ACL ,产生一个副本

	/*
	 * Normal case: get the relation's ACL from pg_class
	 */
	ownerId = classForm->relowner;

	aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl,
							   &isNull);
	if (isNull)
	{
		/* No ACL, so build default ACL */
		switch (classForm->relkind)
		{
			case RELKIND_SEQUENCE:
				acl = acldefault(OBJECT_SEQUENCE, ownerId);
				break;
			default:
				acl = acldefault(OBJECT_TABLE, ownerId);
				break;
		}
		aclDatum = (Datum) 0;
	}
	else
	{
		/* detoast rel's ACL if necessary */
		acl = DatumGetAclP(aclDatum);
	}

如果是NULL则设置默认ACL。

然后调用 aclmask 获取用户在该表的操作权限集。如果操作用户是该表的拥有者,则有该表的所有权限。若不是该表的拥有者,则检查用户被被授予的权限,以及表的公共权限。

	result = aclmask(acl, roleid, ownerId, mask, how);
	/*
	 * Check if ACL_SELECT is being checked and, if so, and not set already as
	 * part of the result, then check if the user is a member of the
	 * pg_read_all_data role, which allows read access to all relations.
	 */
	if (mask & ACL_SELECT && !(result & ACL_SELECT) &&
		has_privs_of_role(roleid, ROLE_PG_READ_ALL_DATA))
		result |= ACL_SELECT;

	/*
	 * Check if ACL_INSERT, ACL_UPDATE, or ACL_DELETE is being checked and, if
	 * so, and not set already as part of the result, then check if the user
	 * is a member of the pg_write_all_data role, which allows
	 * INSERT/UPDATE/DELETE access to all relations (except system catalogs,
	 * which requires superuser, see above).
	 */
	if (mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE) &&
		!(result & (ACL_INSERT | ACL_UPDATE | ACL_DELETE)) &&
		has_privs_of_role(roleid, ROLE_PG_WRITE_ALL_DATA))
		result |= (mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE));

ACL 更新

对某个对象进行权限授予/回收,实际上就是在 ACL 上进行添加或删除指定的权限,同时更新 ACL 。将对应的权限标志位置 0 或 1 即可。

在该过程中需要注意的一点是,会调用 src/backend/utils/adt/acl.c 中的函数 check_circularity(const Acl *old_acl, const AclItem *mod_aip, Oid ownerId),通过递归删除所有被授予者拥有的可再授予权限,检查权限授予是否会有环形成。

通过 old_acl 去 copy 一个副本 acl 调用 aclupdate 进行递归删除操作。

	/* Zap all grant options of target grantee, plus what depends on 'em */
cc_restart:
	num = ACL_NUM(acl);
	aip = ACL_DAT(acl);
	for (i = 0; i < num; i++)
	{
		if (aip[i].ai_grantee == mod_aip->ai_grantee &&
			ACLITEM_GET_GOPTIONS(aip[i]) != ACL_NO_RIGHTS)
		{
			Acl		   *new_acl;

			/* We'll actually zap ordinary privs too, but no matter */
			new_acl = aclupdate(acl, &aip[i], ACL_MODECHG_DEL,
								ownerId, DROP_CASCADE);

			pfree(acl);
			acl = new_acl;

			goto cc_restart;
		}
	}

删除完成后,检查授予者是否还有可在授予权限,如果没有,则授予者不能进行授权。

对象权限管理

对象的权限管理主要是通过使用 SQL 命令 GRANT 和 REVOKE 授予或回收一个或多个角色在对象上的权限。

Grant 语法:

GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
    [, ...] | ALL [ PRIVILEGES ] }
    ON { [ TABLE ] table_name [, ...]
         | ALL TABLES IN SCHEMA schema_name [, ...] }
    TO role_specification [, ...] [ WITH GRANT OPTION ]
    [ GRANTED BY role_specification ]

Revoke 语法:

REVOKE [ GRANT OPTION FOR ]
    { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
    [, ...] | ALL [ PRIVILEGES ] }
    ON { [ TABLE ] table_name [, ...]
         | ALL TABLES IN SCHEMA schema_name [, ...] }
    FROM role_specification [, ...]
    [ GRANTED BY role_specification ]
    [ CASCADE | RESTRICT ]

上述只是展示了其中的一种用法,详细语法规则感兴趣的小伙伴可以查看官方文档:
http://postgres.cn/docs/15/sql-grant.html
http://postgres.cn/docs/15/sql-revoke.html

GRANT/REVOKE 命令都是由函数 ExecuteGrantStmt 实现,该函数只有一个类型为 GrantStmt 的参数。

但函数首先会将 GrantStmt 的参数分别转换为 InternalGrant 结构,将命令的权限列表转化成内部的 AclMode 表示。两个结构如下:

typedef struct GrantStmt
{
	NodeTag		type;
	bool		is_grant;		/* true = GRANT, false = REVOKE */
	GrantTargetType targtype;	/* type of the grant target */
	ObjectType	objtype;		/* kind of object being operated on */
	List	   *objects;		/* list of RangeVar nodes, ObjectWithArgs
								 * nodes, or plain names (as String values) */
	List	   *privileges;		/* list of AccessPriv nodes */
	/* privileges == NIL denotes ALL PRIVILEGES */
	List	   *grantees;		/* list of RoleSpec nodes */
	bool		grant_option;	/* grant or revoke grant option */
	RoleSpec   *grantor;
	DropBehavior behavior;		/* drop behavior (for REVOKE) */
} GrantStmt;
/*
 * The information about one Grant/Revoke statement, in internal format: object
 * and grantees names have been turned into Oids, the privilege list is an
 * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
 * all_privs is true, 'privileges' will be internally set to the right kind of
 * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
 * InternalGrant struct!)
 *
 * Note: 'all_privs' and 'privileges' represent object-level privileges only.
 * There might also be column-level privilege specifications, which are
 * represented in col_privs (this is a list of untransformed AccessPriv nodes).
 * Column privileges are only valid for objtype OBJECT_TABLE.
 */
typedef struct
{
	bool		is_grant;
	ObjectType	objtype;
	List	   *objects;
	bool		all_privs;
	AclMode		privileges;
	List	   *col_privs;
	List	   *grantees;
	bool		grant_option;
	DropBehavior behavior;
} InternalGrant;

当 privileges 取值为 NIL 时,表示授予或回收所有的权限,此时 InternalGrant 的 all_privs 字段为true,且 InternalGrant 的 privileges 字段被设置为 ACL_NO_RIGHTS 也表示 ACL_ALL_RIGHTS。

InternalGrant 中的 all_privs 和 privileges 只表示对象级的权限集合。列级的权限集合是用 col_privs 表示的,它的值通过 GrantStmt 中的 privileges 给出。

GrantStmt 中的 grantees 链表为 PrivGrantee 结构,而 InternalStmt 中为 OID 结构,在此也会进行转换,若 PrivGrantee 结构中 rolename 为空,则会将 ACL_ID_PUBLIC 加入到 OID 链表中。

转换完成之后,通过函数 ExecGrantStmt_oids(&istmt) 判断操作对象的类型,并调用相应的授予或回收权限函数。

以对象是表为例:

表的权限管理函数为 ExecGrant_Relation(istmt)。基本流程图如下(取自《PostgreSQL 数据库内核分析》):
在这里插入图片描述
先打开 pg_class 和 pg_attribute 系统表

	relation = table_open(RelationRelationId, RowExclusiveLock);
	attRelation = table_open(AttributeRelationId, RowExclusiveLock);

接着,获取第一个对象表的 pg_class 元组,

		tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
		if (!HeapTupleIsValid(tuple))
			elog(ERROR, "cache lookup failed for relation %u", relOid);
		pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);

通过 pg_class_tuple->kind 和 istmt->objtype 检查元组类型和对象类型

pg_class_tuple 中获取对象表的 ownerId 和 ACL ,若不存在 ACL ,就新建一个 ACL ,并将默认的权限信息赋给该 ACL(根据对象的不同,默认权限也会不同)。若存在,则直接将旧的 ACL 存储为一个副本。

生成新的 ACL 时,如果是授予权限,则将命令中给出的权限添加到就的 ACL 中;如果是撤销权限,则将命令中的给出的要回收的权限从旧的 ACL 删除。

通过函数 merge_acl_with_grant 来获取新的 ACL ,其中调用 aclupdate ,在 ACL 中搜索该受权者和授予者的现有条目。如果存在,只需就地修改条目;否则,在末尾插入新条目。

	for (dst = 0; dst < num; ++dst)
	{
		if (aclitem_match(mod_aip, old_aip + dst))
		{
			/* found a match, so modify existing item */
			new_acl = allocacl(num);
			new_aip = ACL_DAT(new_acl);
			memcpy(new_acl, old_acl, ACL_SIZE(old_acl));
			break;
		}
	}

	if (dst == num)
	{
		/* need to append a new item */
		new_acl = allocacl(num + 1);
		new_aip = ACL_DAT(new_acl);
		memcpy(new_aip, old_aip, num * sizeof(AclItem));

		/* initialize the new entry with no permissions */
		new_aip[dst].ai_grantee = mod_aip->ai_grantee;
		new_aip[dst].ai_grantor = mod_aip->ai_grantor;
		ACLITEM_SET_PRIVS_GOPTIONS(new_aip[dst],
								   ACL_NO_RIGHTS, ACL_NO_RIGHTS);
		num++;					/* set num to the size of new_acl */
	}

根据修改模式进行相应条目的权限修改

	/* apply the specified permissions change */
	switch (modechg)
	{
		case ACL_MODECHG_ADD:
			ACLITEM_SET_RIGHTS(new_aip[dst],
							   old_rights | ACLITEM_GET_RIGHTS(*mod_aip));
			break;
		case ACL_MODECHG_DEL:
			ACLITEM_SET_RIGHTS(new_aip[dst],
							   old_rights & ~ACLITEM_GET_RIGHTS(*mod_aip));
			break;
		case ACL_MODECHG_EQL:
			ACLITEM_SET_RIGHTS(new_aip[dst],
							   ACLITEM_GET_RIGHTS(*mod_aip));
			break;
	}

新的 ACL 生成之后,将其插入元组中,更新到 pg_class 中相应元组,

接下来就是依次处理列权限,调用函数 expand_col_privileges 将列权限列表转换成数组形式表示。依次判断每一列的权限,调用函数 ExecGrant_Attribute 计算每一列实际可以授予或回收的权限,然后更新 ACL。

每一列的 ACL 存放在该列对应的 pg_attribute 元组中,ExecGrant_Attribute 的处理过程与 ExecGrant_Relation 类似。但不同的是,目前只有表中才会有列级权限,所以 ExecGrant_Attribute 只能从 ExecGrant_Relation 处调用,而不能直接被 ExecGrandStmt 调用。

如果只有一个表对象则结束;如果有多个,则获取第二个表对象,继续处理。

对象权限检查

在对数据库对象操作时,必须要对该对象上的权限进行检查,只有拥有该操作权限时,才可以执行该操作。

例如,用户要查询表 table_test ,可以执行命令:SELECT * FROM table_test;。如果该用户在 table_test 上不具有查询权限,则不可以查询。通常数据库对象的拥有者具有该对象的一切操作权限,超级用户也拥有对所有对象的全部操作权限,而非属主用户在对数据库对象操作之前需要进行权限检查。

以表的检查权限为例。表上的权限检查由函数 ExecCheckRTEPerms 实现,该函数参数为 RangeTblEntry 。

typedef struct RangeTblEntry
{
	NodeTag		type;
	RTEKind		rtekind;		/* 对象类型 */
	······
	AclMode		requiredPerms;	/* 需要的访问权限( AclMode 类型) */
	Oid			checkAsUser;	/* 角色 ID */
	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
	Bitmapset  *insertedCols;	/* columns needing INSERT permission */
	Bitmapset  *updatedCols;	/* columns needing UPDATE permission */
	Bitmapset  *extraUpdatedCols;	/* generated columns being updated */
	List	   *securityQuals;	/* security barrier quals to apply, if any */
} RangeTblEntry;

其中,字段 requiredPerms 表示需要访问的权限信息。 selectedCols 表示在表的操作中需要有 SELECT 权限的列的集合, insertedCols 则表示需要INSERT权限的列集合, 其他同理。

函数 ExecCheckRTEPerms 的基本流程如下(取自《PostgreSQL 数据库内核分析》):
在这里插入图片描述
首先获取需要的权限,通过传入的参数获取。

	/*
	 * No work if requiredPerms is empty.
	 */
	requiredPerms = rte->requiredPerms;
	if (requiredPerms == 0)
		return true;

调用 aclmask 获取已有权限,原理是获取目标的 acl 中 AclItem 链表的个数和节点,依次去遍历链表,判断 AclItem 结构中当前用户直接被授予的权限。

	num = ACL_NUM(acl);
	aidat = ACL_DAT(acl);

	/*
	 * Check privileges granted directly to roleid or to public
	 */
	for (i = 0; i < num; i++)
	{
		AclItem    *aidata = &aidat[i];

		if (aidata->ai_grantee == ACL_ID_PUBLIC ||
			aidata->ai_grantee == roleid)
		{
			result |= aidata->ai_privs & mask;
			if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
				return result;
		}
	}

调用 pg_class_aclmask 的进行判断,relPerms 是操作用户拥有的权限与请求权限 & 的结果:

	/*
	 * We must have *all* the requiredPerms bits, but some of the bits can be
	 * satisfied from column-level rather than relation-level permissions.
	 * First, remove any bits that are satisfied by relation permissions.
	 */
	relPerms = pg_class_aclmask(relOid, userid, requiredPerms, ACLMASK_ALL);
	remainingPerms = requiredPerms & ~relPerms;
	if (remainingPerms != 0)

requiredPerms 和 ~relPerms 按位相与,若结果 remainingPerms 等于 0,则表示已有权限满足所需权限,检查结束,若不等于 0,进一步检查列级权限。

		/*
		 * If we lack any permissions that exist only as relation permissions,
		 * we can fail straight away.
		 */
		if (remainingPerms & ~(ACL_SELECT | ACL_INSERT | ACL_UPDATE))
			return false;

首先判断列级权限中有无 SELECT/UPDATE/INSERT,若没有返回 false,若有则进一步通过 requiredPerms 和SELECT/UPDATE/INSERT 依次相与,检查具体是那一个或那几个权限,并调用函数 pg_attribute_aclcheck 进行列级权限检查。

添加一个新的权限位

暂时没有,后面进行补充

  • 38
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
回答: 要在项目中整合PostgreSQL和MyBatis-Plus,你需要进行以下几个步骤。 首先,你需要在项目的pom.xml文件中添加MyBatis-Plus和PostgreSQL的依赖项。在依赖项中,你需要添加以下代码段:\[1\] ```xml <!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dependency> <!-- postgresql --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> ``` 接下来,你需要在项目的application.yml文件中进行配置。你需要设置数据库的连接信息,包括URL、用户名和密码。此外,你还需要设置schema的名称。以下是一个示例配置:\[2\] ```yaml spring: datasource: platform: postgres url: jdbc:postgresql://192.188.1.245:5432/uum?currentSchema=uum schemaName: uum username: xxxx password: xxxx driver-class-name: org.postgresql.Driver ``` 最后,你需要在数据库中创建自增字段。在PostgreSQL中,你可以使用sequence来实现自增字段的功能。以下是一个示例的SQL语句:\[3\] ```sql create sequence uum.userid_seq start with 1 increment by 1 no minvalue no maxvalue cache 1; alter sequence uum.userid_seq owner to smartsys; alter table uum.user alter column id set default nextval('uum.userid_seq'); ``` 通过以上步骤,你就可以成功地将PostgreSQL和MyBatis-Plus整合在一起了。你可以使用MyBatis-Plus提供的功能来进行数据库操作。 #### 引用[.reference_title] - *1* [springboot 整合 mybatis plus postgresql](https://blog.csdn.net/weixin_41010294/article/details/105710247)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [MybatisPlus+Postgresql整合的几个坑](https://blog.csdn.net/xuruilll/article/details/122670781)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aSimpleSheep

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值