假设我有一个项目表:
CREATE TABLE items
(
item serial PRIMARY KEY,
...
);
Run Code Online (Sandbox Code Playgroud)
现在,我想为每个项目介绍“权限”的概念(请注意,我在这里不是在谈论数据库访问权限,而是该项目的业务逻辑权限)。每个项目都具有默认权限以及可以覆盖默认权限的每用户权限。
我试图想出几种方法来实现这一点,并提出了以下解决方案,但我不确定哪个是最好的以及为什么:
为每个权限使用布尔列:
CREATE TABLE items
(
item serial PRIMARY KEY,
can_change_description boolean NOT NULL,
can_change_price boolean NOT NULL,
can_delete_item_from_store boolean NOT NULL,
...
);
CREATE TABLE item_per_user_permissions
(
item int NOT NULL REFERENCES items(item),
user int NOT NULL REFERENCES users(user),
PRIMARY KEY(item, user),
can_change_description boolean NOT NULL,
can_change_price boolean NOT NULL,
can_delete_item_from_store boolean NOT NULL,
...
);
Run Code Online (Sandbox Code Playgroud)
优点:每个权限都是命名的。
缺点:有几十个权限会显着增加列数,并且您必须定义它们两次(每个表中一次)。
使用整数并将其视为位域(即位 0 表示can_change_description
,位 1 表示can_change_price
,依此类推,并使用按位运算来设置或读取权限)。
CREATE DOMAIN permissions AS integer;
Run Code Online (Sandbox Code Playgroud)
优点:非常快。
缺点:你必须在数据库和前端界面中跟踪哪个位代表哪个权限。
与 2) 相同,但使用bit(n)
. 很可能具有相同的优点和缺点,可能稍微慢一些。
对权限使用枚举类型:
CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);
Run Code Online (Sandbox Code Playgroud)
然后为默认权限创建一个额外的表:
CREATE TABLE item_default_permissions
(
item int NOT NULL REFERENCES items(item),
perm permission NOT NULL,
PRIMARY KEY(item, perm)
);
Run Code Online (Sandbox Code Playgroud)
并将每用户定义表更改为:
CREATE TABLE item_per_user_permissions
(
item int NOT NULL REFERENCES items(item),
user int NOT NULL REFERENCES users(user),
perm permission NOT NULL,
PRIMARY KEY(item, user, perm)
);
Run Code Online (Sandbox Code Playgroud)
优点:易于命名单个权限(您不必处理位位置)。
缺点:即使只是检索默认权限,也需要访问两个附加表:第一,默认权限表,第二,存储枚举值的系统目录。
尤其是因为必须为该项目的每个页面视图检索默认权限,所以最后一个替代方案的性能影响可能会很大。
与 4) 相同,但使用一个数组来保存所有(默认)权限:
CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);
CREATE TABLE items
(
item serial PRIMARY KEY,
granted_permissions permission ARRAY,
...
);
Run Code Online (Sandbox Code Playgroud)
优点:易于命名单个权限(您不必处理位位置)。
缺点:打破第一范式,有点难看。如果权限数量很大(大约 50),则连续占用相当多的字节。
你能想到其他的选择吗?
应该采取哪种方法,为什么?
请注意:这是之前在 Stackoverflow 上发布的问题的修改版本。
我知道您并不是在问数据库安全本身,但是您可以使用数据库安全来做您想做的事情。您甚至可以在 Web 应用程序中使用它。如果您不想使用数据库安全性,那么架构仍然适用。
您需要列级安全性、行级安全性以及可能的分层角色管理。基于角色的安全性比基于用户的安全性更容易管理。
此示例代码适用于即将发布的 PostgreSQL 9.4。您可以使用 9.3 来完成,但需要更多的体力劳动。
如果您关心性能†,您希望所有内容都可索引,您应该这样做。这意味着位掩码和数组字段可能不是一个好主意。
在此示例中,我们将主要数据表保留在data
架构中,并将相应的视图保留在public
.
create schema data; --main data tables
create schema security; --acls, security triggers, default privileges
create table data.thing (
thing_id int primary key,
subject text not null, --or whatever
owner name not null
);
Run Code Online (Sandbox Code Playgroud)
在 data.thing 上放置触发器以进行插入和更新,强制所有者列是 current_user。也许只允许所有者删除他自己的记录(另一个触发器)。
创建一个WITH CHECK OPTION
视图,这是用户实际使用的视图。努力让它可更新,否则你将需要触发器/规则,这是更多的工作。
create view public.thing with(security_barrier) as
select
thing_id,
subject,
owner,
from data.thing
where
pg_has_role(owner, 'member') --only owner or roles "above" him can view his rows.
WITH CHECK OPTION;
Run Code Online (Sandbox Code Playgroud)
接下来,创建一个访问控制列表表:
--privileges r=read, w=write
create table security.thing_acl (
thing_id int,
grantee name, --the role to whom your are granting the privilege
privilege char(1) check (privilege in ('r','w') ),
primary key (thing_id, grantee, privilege),
foreign key (thing_id) references data.thing(thing_id) on delete cascade
);
Run Code Online (Sandbox Code Playgroud)
更改您的视图以考虑 ACL:
drop view public.thing;
create view public.thing with(security_barrier) as
select
thing_id,
subject,
owner
from data.thing a
where
pg_has_role(owner, 'member')
or exists (select 1 from security.thing_acl b where b.thing_id = a.thing_id and pg_has_role(grantee, 'member') and privilege='r')
with check option;
Run Code Online (Sandbox Code Playgroud)
创建一个默认的行权限表:
create table security.default_row_privileges (
table_name name,
role_name name,
privilege char(1),
primary key (table_name, role_name, privilege)
);
Run Code Online (Sandbox Code Playgroud)
在 data.thing 上插入一个触发器,以便它将默认行权限复制到 security.thing_acl 。
grantor
和admin_option
列以跟踪授予权限的人,以及被授予者是否可以管理该行的权限。† 在这种情况下 pg_has_role 可能是不可索引的。您必须获得 current_user 的所有高级角色的列表,并与所有者/被授予者的值进行比较。
小智 5
您是否考虑过使用访问控制列表PostgreSQL 扩展?
它包含本机 PostgreSQL 数据类型 ACE 和一组允许您检查用户是否有权访问数据的函数。它适用于 PostgreSQL 角色系统或代表您的应用程序用户/角色 ID 的抽象数字(或 UUID)。
在您的情况下,您只需向数据表中添加一个 ACL 列,并使用其中一个acl_check_access
函数根据 ACL 检查用户。
CREATE TABLE items
(
item serial PRIMARY KEY,
acl ace[],
...
);
INSERT INTO items(acl, ...) VALUES ('{a//<user id>=r, a//<role id>=rwd, ...}');
SELECT * FROM items where acl_check_access(acl, 'r', <roles of the user>, false) = 'r'
Run Code Online (Sandbox Code Playgroud)
使用 ACL 是处理业务逻辑权限的一种极其灵活的方式。此外,它非常快——平均开销仅为读取记录所需时间的 25%。唯一的限制是它支持每个对象类型最多 16 个自定义权限。
归档时间: |
|
查看次数: |
7438 次 |
最近记录: |