Làm cách nào để triển khai các quyền logic nghiệp vụ trong PostgreSQL (hoặc SQL nói chung)?


16

Giả sử tôi có một bảng các mặt hàng:

CREATE TABLE items
(
    item serial PRIMARY KEY,
    ...
);

Bây giờ, tôi muốn giới thiệu khái niệm "quyền" cho từng mục (xin lưu ý, tôi không nói về quyền truy cập cơ sở dữ liệu ở đây, nhưng quyền logic kinh doanh cho mục đó). Mỗi mục có quyền mặc định và quyền cho mỗi người dùng có thể ghi đè quyền mặc định.

Tôi đã cố gắng nghĩ ra một số cách để thực hiện điều này và đưa ra các giải pháp sau đây, nhưng tôi không chắc đâu là cách tốt nhất và tại sao:

1) Giải pháp Boolean

Sử dụng cột boolean cho mỗi quyền:

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,
    ...
);

Ưu điểm : Mỗi quyền được đặt tên.

Nhược điểm : Có hàng tá quyền làm tăng đáng kể số lượng cột và bạn phải xác định chúng hai lần (một lần trong mỗi bảng).

2) Giải pháp số nguyên

Sử dụng một số nguyên và coi nó như một bitfield (nghĩa là bit 0 dành cho can_change_description, bit 1 là dành cho can_change_price, v.v. và sử dụng các thao tác bitwise để đặt hoặc đọc quyền).

CREATE DOMAIN permissions AS integer;

Ưu điểm : rất nhanh.

Nhược điểm : Bạn phải theo dõi bit nào đại diện cho quyền nào trong cả cơ sở dữ liệu và giao diện mặt trước.

3) Giải pháp Bitfield

Tương tự như 2), nhưng sử dụng bit(n). Rất có thể là những lợi thế và bất lợi tương tự, có thể chậm hơn một chút.

4) Giải pháp Enum

Sử dụng một loại enum cho các quyền:

CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);

và sau đó tạo một bảng phụ cho các quyền mặc định:

CREATE TABLE item_default_permissions
(
    item int NOT NULL REFERENCES items(item),
    perm permission NOT NULL,

    PRIMARY KEY(item, perm)
);

và thay đổi bảng định nghĩa cho mỗi người dùng thành:

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)    
);

Ưu điểm : Dễ dàng đặt tên cho các quyền riêng lẻ (bạn không phải xử lý các vị trí bit).

Nhược điểm : Ngay cả khi chỉ lấy các quyền mặc định, nó vẫn yêu cầu truy cập hai bảng bổ sung: thứ nhất, bảng quyền mặc định và thứ hai, danh mục hệ thống lưu trữ các giá trị enum.

Đặc biệt vì các quyền mặc định phải được truy xuất cho mỗi lần xem trang duy nhất của mục đó , tác động hiệu suất của phương án cuối cùng có thể là đáng kể.

5) Giải pháp mảng Enum

Tương tự như 4), nhưng sử dụng một mảng để giữ tất cả các quyền (mặc định):

CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);

CREATE TABLE items
(
    item serial PRIMARY KEY,

    granted_permissions permission ARRAY,
    ...
);

Ưu điểm : Dễ dàng đặt tên cho các quyền riêng lẻ (bạn không phải xử lý các vị trí bit).

Nhược điểm : Phá vỡ hình thức bình thường thứ 1 và hơi xấu xí. Chiếm một số lượng đáng kể các byte liên tiếp nếu số lượng quyền lớn (khoảng 50).

Bạn có thể nghĩ về các lựa chọn thay thế khác?

Cách tiếp cận nào nên được thực hiện và tại sao?

Xin lưu ý: đây là phiên bản sửa đổi của câu hỏi được đăng trước đó trên Stackoverflow .


2
Với hàng tá quyền khác nhau, tôi có thể chọn một (hoặc nhiều) biginttrường (mỗi trường tốt cho 64 bit) hoặc chuỗi bit. Tôi đã viết một vài câu trả lời liên quan về SO có thể giúp ích.
Erwin Brandstetter

Câu trả lời:


7

Tôi biết rằng bạn không yêu cầu về an ninh cơ sở dữ liệu cho mỗi gia nhập , nhưng bạn có thể làm những gì bạn muốn sử dụng bảo mật cơ sở dữ liệu. Bạn thậm chí có thể sử dụng điều này trong một ứng dụng web. Nếu bạn không muốn sử dụng bảo mật cơ sở dữ liệu, thì các lược đồ vẫn được áp dụng.

Bạn muốn bảo mật cấp cột, bảo mật cấp hàng và có thể quản lý vai trò phân cấp. Bảo mật dựa trên vai trò dễ quản lý hơn nhiều so với bảo mật dựa trên người dùng.

Mã ví dụ này dành cho PostgreQuery 9.4, sẽ sớm ra mắt. Bạn có thể làm điều đó với 9.3, nhưng cần nhiều lao động thủ công hơn.

Bạn muốn mọi thứ đều có thể lập chỉ mục nếu bạn quan tâm đến hiệu suất, mà bạn nên làm. Điều này có nghĩa là các trường mặt nạ bit và mảng có thể sẽ không phải là một ý tưởng tốt.

Trong ví dụ này, chúng tôi giữ các bảng dữ liệu chính trong datalược đồ và các khung nhìn tương ứng 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
);

Đặt một kích hoạt trên data.thing để chèn và cập nhật thực thi rằng cột chủ sở hữu là current_user. Có lẽ chỉ cho phép chủ sở hữu xóa hồ sơ của chính mình (một kích hoạt khác).

Tạo một WITH CHECK OPTIONkhung nhìn, đó là những gì người dùng sẽ thực sự sử dụng. Cố gắng thực sự khó khăn để làm cho nó có thể cập nhật, nếu không, bạn sẽ cần kích hoạt / quy tắc, đó là công việc nhiều hơn.

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;

Tiếp theo, tạo bảng danh sách kiểm soát truy cập:

--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
);

Thay đổi chế độ xem của bạn thành tài khoản cho 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;

Tạo bảng đặc quyền hàng mặc định:

create table security.default_row_privileges (
  table_name name,
  role_name name,
  privilege char(1),

  primary key (table_name, role_name, privilege)
);

Đặt một kích hoạt khi chèn vào data.thing để nó sao chép các đặc quyền hàng mặc định thành security.thing_acl.

  • Điều chỉnh bảo mật cấp bảng một cách thích hợp (ngăn không cho người dùng không mong muốn). Không ai có thể đọc dữ liệu hoặc lược đồ bảo mật.
  • Điều chỉnh bảo mật cấp cột một cách thích hợp (ngăn một số người dùng nhìn thấy / chỉnh sửa một số cột). Bạn có thể sử dụng has_column_priv đặc biệt () để kiểm tra xem người dùng có thể thấy một cột không.
  • Có lẽ muốn thẻ definer bảo mật trên quan điểm của bạn.
  • Xem xét việc thêm grantoradmin_optioncác cột vào các bảng acl để theo dõi ai đã cấp đặc quyền và liệu người được cấp có thể quản lý các đặc quyền trên hàng đó hay không.
  • Kiểm tra lô

Trong trường hợp này pg_has_role có lẽ không thể lập chỉ mục. Bạn sẽ phải có được một danh sách tất cả các vai trò ưu việt cho current_user và so sánh với giá trị chủ sở hữu / người được cấp thay thế.


Bạn có thấy phần " Tôi không nói về quyền truy cập cơ sở dữ liệu ở đây " không?
a_horse_with_no_name

@a_horse_with_no_name có tôi đã làm. Anh ta có thể viết hệ thống RLS / ACL của riêng mình hoặc anh ta có thể sử dụng bảo mật tích hợp sẵn của cơ sở dữ liệu để làm những gì anh ta yêu cầu.
Neil McGuigan

Cảm ơn bạn đã trả lời chi tiết của bạn! Tuy nhiên, tôi không nghĩ sử dụng vai trò cơ sở dữ liệu là câu trả lời đúng ở đây vì không chỉ nhân viên mà còn mỗi người dùng có thể có quyền. Ví dụ sẽ là 'can_view_item', 'can_bulk_order_item' hoặc 'can_Vview_item'. Tôi nghĩ rằng sự lựa chọn ban đầu của tôi về tên quyền đã khiến bạn tin rằng đó chỉ là về quyền của nhân viên, nhưng tất cả những tên này chỉ là ví dụ để trừu tượng hóa sự phức tạp. Như tôi đã nói trong câu hỏi ban đầu, đó là về quyền của mỗi người dùng , không phải cho mỗi quyền của nhân viên .
JohnCand

Dù sao, việc phải có các vai trò cơ sở dữ liệu riêng biệt cho mỗi hàng người dùng trong bảng người dùng dường như là quá mức cần thiết và khó quản lý. Tuy nhiên, tôi nghĩ rằng câu trả lời của bạn có giá trị đối với các nhà phát triển chỉ thực hiện các quyền của nhân viên.
JohnCand

1
@ John John Tôi thực sự không thấy cách quản lý quyền ở nơi khác dễ dàng hơn, nhưng vui lòng chỉ cho chúng tôi giải pháp của bạn sau khi bạn tìm thấy nó! :)
Neil McGuigan

4

Bạn đã cân nhắc sử dụng tiện ích mở rộng PostgreQuery của Danh sách điều khiển truy cập chưa?

Nó chứa kiểu dữ liệu gốc PostgreSQL ACE và một bộ các chức năng cho phép bạn kiểm tra xem người dùng có được phép truy cập dữ liệu hay không. Nó hoạt động với hệ thống vai trò PostgreSQL hoặc với các số trừu tượng (hoặc UUID) đại diện cho ID người dùng / vai trò ứng dụng của bạn.

Trong trường hợp của bạn, bạn chỉ cần thêm một cột ACL vào các bảng dữ liệu của mình và sử dụng một trong các acl_check_accesschức năng để kiểm tra người dùng đối với 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'

Sử dụng ACL là một cách cực kỳ linh hoạt để xử lý các quyền logic nghiệp vụ. Ngoài ra, nó cực kỳ nhanh - chi phí trung bình chỉ bằng 25% thời gian cần thiết để đọc một bản ghi. Hạn chế duy nhất là nó hỗ trợ tối đa 16 quyền tùy chỉnh cho mỗi loại đối tượng.


1

Tôi có thể nghĩ về một khả năng khác để mã hóa cái này, cái quan hệ

Nếu bạn không cần permission_per_itembàn, bạn có thể bỏ qua và kết nối PermissionsItemstrực tiếp với item_per_user_permissionsbảng.

nhập mô tả hình ảnh ở đây

sơ đồ truyền thuyết

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.