วิธีใช้สิทธิ์ตรรกะทางธุรกิจใน PostgreSQL (หรือ SQL โดยทั่วไป)


16

สมมติว่าฉันมีสารบัญ

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

ตอนนี้ฉันต้องการแนะนำแนวคิดของ "การอนุญาต" สำหรับแต่ละรายการ (โปรดทราบว่าฉันไม่ได้พูดถึงสิทธิ์การเข้าถึงฐานข้อมูลที่นี่ แต่สิทธิ์ทางตรรกะทางธุรกิจสำหรับรายการนั้น) แต่ละรายการมีสิทธิ์เริ่มต้นและสิทธิ์ต่อผู้ใช้ที่อาจแทนที่สิทธิ์เริ่มต้น

ฉันพยายามคิดหลายวิธีในการใช้งานและหาวิธีแก้ไขปัญหาต่อไปนี้ แต่ฉันไม่แน่ใจว่าวิธีใดดีที่สุดและเพราะเหตุใด:

1) โซลูชันบูลีน

ใช้คอลัมน์บูลีนสำหรับการอนุญาตแต่ละครั้ง:

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

ข้อดี : แต่ละสิทธิ์มีชื่อ

ข้อเสีย : มีหลายสิทธิ์ที่เพิ่มจำนวนคอลัมน์อย่างมีนัยสำคัญและคุณต้องกำหนดพวกเขาสองครั้ง (หนึ่งครั้งในแต่ละตาราง)

2) โซลูชั่นจำนวนเต็ม

ใช้จำนวนเต็มและถือว่าเป็นบิตฟิลด์ (เช่นบิต 0 สำหรับcan_change_descriptionบิต 1 สำหรับcan_change_priceและอื่น ๆ และใช้การดำเนินการระดับบิตเพื่อตั้งค่าหรืออ่านสิทธิ์)

CREATE DOMAIN permissions AS integer;

ข้อดี : เร็วมาก

ข้อเสีย : คุณต้องติดตามว่าบิตใดที่ใช้แทนทั้งสิทธิ์ในฐานข้อมูลและส่วนต่อประสานส่วนหน้า

3) โซลูชัน Bitfield

เช่นเดียวกับ 2) bit(n)แต่การใช้ ส่วนใหญ่แล้วข้อดีและข้อเสียเดียวกันอาจช้าลงเล็กน้อย

4) โซลูชัน Enum

ใช้ประเภท enum สำหรับการอนุญาต:

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

จากนั้นสร้างตารางพิเศษสำหรับการอนุญาตเริ่มต้น:

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

    PRIMARY KEY(item, perm)
);

และเปลี่ยนตารางนิยามต่อผู้ใช้เป็น:

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

ข้อดี : ง่ายต่อการตั้งชื่อการอนุญาตส่วนบุคคล (คุณไม่ต้องจัดการตำแหน่งบิต)

ข้อเสีย : แม้จะเพิ่งรับสิทธิ์เริ่มต้นก็ต้องเข้าถึงสองตารางเพิ่มเติม: ครั้งแรกตารางสิทธิ์เริ่มต้นและที่สองแคตตาล็อกระบบการจัดเก็บค่า enum

โดยเฉพาะอย่างยิ่งเนื่องจากต้องมีการดึงสิทธิ์เริ่มต้นสำหรับทุกการดูหน้าเดียวของรายการนั้นผลกระทบด้านประสิทธิภาพของตัวเลือกสุดท้ายอาจมีความสำคัญ

5) โซลูชัน Enum Array

เหมือนกับ 4) แต่ใช้อาร์เรย์เพื่อเก็บสิทธิ์ (ค่าเริ่มต้น) ทั้งหมด:

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

CREATE TABLE items
(
    item serial PRIMARY KEY,

    granted_permissions permission ARRAY,
    ...
);

ข้อดี : ง่ายต่อการตั้งชื่อการอนุญาตส่วนบุคคล (คุณไม่ต้องจัดการตำแหน่งบิต)

ข้อเสีย : แบ่งรูปแบบปกติที่ 1 และเป็นบิตที่น่าเกลียด ใช้จำนวนไบต์ที่มากพอในแถวหากจำนวนสิทธิ์มีขนาดใหญ่ (ประมาณ 50)

คุณนึกถึงทางเลือกอื่น ๆ ได้ไหม?

ควรใช้วิธีใดและทำไม

กรุณาหมายเหตุ: นี่เป็นรุ่นล่าสุดของคำถามที่โพสต์ก่อนหน้านี้เมื่อ Stackoverflow


2
ด้วยสิทธิ์ที่แตกต่างกันหลายสิบฉันอาจเลือกหนึ่ง (หรือมากกว่า) bigintฟิลด์ (แต่ละที่ดีสำหรับ 64 บิต) หรือบิตสตริง ฉันเขียนคำตอบที่เกี่ยวข้องสองสามข้อเกี่ยวกับ SO ที่อาจช่วยได้
Erwin Brandstetter

คำตอบ:


7

ฉันรู้ว่าคุณไม่ได้ถามเกี่ยวกับการรักษาความปลอดภัยฐานข้อมูลต่อ seแต่คุณสามารถทำสิ่งที่คุณต้องการโดยใช้ฐานข้อมูลความปลอดภัย คุณสามารถใช้สิ่งนี้ได้ในเว็บแอพ หากคุณไม่ต้องการใช้ความปลอดภัยฐานข้อมูลดังนั้นสกีมายังคงมีผล

คุณต้องการความปลอดภัยระดับคอลัมน์ความปลอดภัยระดับแถวและการจัดการบทบาทแบบลำดับชั้น การรักษาความปลอดภัยตามบทบาทนั้นง่ายต่อการจัดการมากกว่าการรักษาความปลอดภัยตามผู้ใช้

โค้ดตัวอย่างนี้สำหรับ PostgreSQL 9.4 ซึ่งจะออกมาเร็ว ๆ นี้ คุณสามารถทำได้ด้วย 9.3 แต่จำเป็นต้องใช้แรงงานคนมากกว่า

คุณต้องการให้ทุกสิ่งสามารถสร้างดัชนีได้หากคุณกังวลกับประสิทธิภาพ performance ซึ่งคุณควรจะเป็น ซึ่งหมายความว่าเขตข้อมูลบิตมาสก์และอาร์เรย์อาจไม่ใช่ความคิดที่ดี

ในตัวอย่างนี้เราเก็บตารางข้อมูลหลักไว้ใน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
);

ใส่ทริกเกอร์ใน 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;

ถัดไปสร้างตารางรายการควบคุมการเข้าถึง:

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

เปลี่ยนมุมมองของคุณเป็นบัญชีสำหรับ 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;

สร้างตารางสิทธิ์แถวเริ่มต้น:

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

  primary key (table_name, role_name, privilege)
);

ใส่ทริกเกอร์ในการแทรกข้อมูลทุกอย่างเพื่อคัดลอกสิทธิ์แถวเริ่มต้นไปที่การรักษาความปลอดภัยทุกอย่าง

  • ปรับความปลอดภัยระดับโต๊ะให้เหมาะสม (ป้องกันการแทรกจากผู้ใช้ที่ไม่ต้องการ) ไม่มีใครสามารถอ่านข้อมูลหรือสกีมาความปลอดภัยได้
  • ปรับความปลอดภัยระดับคอลัมน์ให้เหมาะสม (ป้องกันไม่ให้ผู้ใช้บางคนเห็น / แก้ไขบางคอลัมน์) คุณสามารถใช้ has_column_privilege () เพื่อตรวจสอบว่าผู้ใช้สามารถเห็นคอลัมน์
  • อาจต้องการแท็กผู้รักษาความปลอดภัยบนมุมมองของคุณ
  • พิจารณาการเพิ่มgrantorและadmin_optionคอลัมน์ลงในตาราง acl เพื่อติดตามผู้ที่ให้สิทธิ์และว่าผู้รับสิทธิ์สามารถจัดการสิทธิ์ในแถวนั้นได้หรือไม่
  • ทดสอบจำนวนมาก

†ในกรณีนี้ pg_has_role อาจไม่สามารถจัดทำดัชนีได้ คุณจะต้องรับรายการบทบาทที่เหนือกว่าทั้งหมดเป็น current_user และเปรียบเทียบกับค่าเจ้าของ / ผู้รับสิทธิ์แทน


คุณเห็นส่วน " ฉันไม่ได้พูดถึงสิทธิ์การเข้าถึงฐานข้อมูลที่นี่ " หรือไม่?
a_horse_with_no_name

@a_horse_with_no_name ใช่ฉันทำ เขาสามารถเขียนระบบ RLS / ACL ของตนเองหรือใช้ความปลอดภัยในตัวของฐานข้อมูลเพื่อทำสิ่งที่เขาขอ
Neil McGuigan

ขอบคุณสำหรับคำตอบโดยละเอียด! อย่างไรก็ตามฉันไม่คิดว่าการใช้บทบาทฐานข้อมูลเป็นคำตอบที่ถูกต้องที่นี่เนื่องจากไม่ใช่แค่พนักงาน แต่ผู้ใช้ทุกคนอาจได้รับอนุญาต ตัวอย่างจะเป็น 'can_view_item', 'can_bulk_order_item' หรือ 'can_review_item' ฉันคิดว่าชื่อตัวเลือกการอนุญาตดั้งเดิมของฉันทำให้คุณเชื่อว่ามันเป็นเพียงแค่การอนุญาตของเจ้าหน้าที่ แต่ชื่อเหล่านี้เป็นเพียงตัวอย่างที่ทำให้เข้าใจถึงความซับซ้อน อย่างที่ฉันพูดไว้ในคำถามเดิมมันเกี่ยวกับการอนุญาตของผู้ใช้ไม่ใช่การอนุญาตของพนักงาน
JohnCand

อย่างไรก็ตามต้องมีบทบาทฐานข้อมูลแยกต่างหากสำหรับทุกแถวผู้ใช้ในตารางผู้ใช้ดูเหมือนว่าจะเกินขีดและจัดการได้ยาก อย่างไรก็ตามฉันคิดว่าคำตอบของคุณมีค่าสำหรับนักพัฒนาที่ใช้สิทธิ์ของพนักงาน
JohnCand

1
@JohnCand ฉันไม่เห็นว่าจะจัดการสิทธิ์ที่อื่นได้ง่ายขึ้นอย่างไร แต่โปรดชี้ให้เราเห็นวิธีการแก้ปัญหาของคุณเมื่อคุณพบมัน! :)
Neil McGuigan

4

คุณได้พิจารณาใช้ส่วนขยาย PostgreSQL Access Control Listหรือไม่?

มันมีข้อมูลท้องถิ่นชนิด PostgreSQL ACE และชุดของฟังก์ชั่นที่ช่วยให้คุณตรวจสอบว่าผู้ใช้มีสิทธิ์ในการเข้าถึงข้อมูล สามารถใช้งานได้กับระบบบทบาท PostgreSQL หรือตัวเลขนามธรรม (หรือ UUIDs) ซึ่งเป็นตัวแทนผู้ใช้ / ID บทบาทของแอปพลิเคชันของคุณ

ในกรณีของคุณคุณเพียงแค่เพิ่มคอลัมน์ 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'

การใช้ ACLs เป็นวิธีที่ยืดหยุ่นอย่างมากในการจัดการกับสิทธิ์ทางตรรกะทางธุรกิจ นอกจากนี้มันยังเร็วอย่างไม่น่าเชื่อ - ค่าใช้จ่ายเฉลี่ยเพียง 25% ของเวลาที่ใช้ในการอ่านบันทึก ข้อ จำกัด เพียงอย่างเดียวคือจะรองรับการอนุญาตที่กำหนดเองสูงสุด 16 รายการต่อประเภทวัตถุ


1

ฉันสามารถคิดความเป็นไปได้อีกอย่างในการเข้ารหัสสิ่งนี้

หากคุณไม่ต้องการpermission_per_itemtableyou คุณสามารถข้ามมันและเชื่อมต่อPermissionsและItemsตรงไปยังitem_per_user_permissionsตาราง

ป้อนคำอธิบายรูปภาพที่นี่

แผนภาพตำนาน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.