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은 for can_change_description, 비트 1은 for can_change_price등). 비트 단위 연산을 사용하여 권한을 설정하거나 읽으십시오.

CREATE DOMAIN permissions AS integer;

장점 : 매우 빠릅니다.

단점 : 데이터베이스와 프론트 엔드 인터페이스에서 어떤 비트가 어떤 권한을 나타내는 지 추적해야합니다.

3) 비트 필드 솔루션

2)와 동일하지만을 사용하십시오 bit(n). 아마도 동일한 장점과 단점, 아마도 약간 느릴 수 있습니다.

4) 열거 형 솔루션

권한에 열거 형을 사용하십시오.

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) 열거 형 배열 솔루션

4)와 동일하지만 배열을 사용하여 모든 (기본) 권한을 보유하십시오.

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

CREATE TABLE items
(
    item serial PRIMARY KEY,

    granted_permissions permission ARRAY,
    ...
);

장점 : 개별 권한의 이름을 쉽게 지정할 수 있습니다 (비트 위치를 처리 할 필요가 없음).

단점 : 첫 번째 정규 형식을 어 기고 약간 추악합니다. 권한 수가 많은 경우 (약 50 개) 행에서 상당한 바이트 수를 차지합니다.

다른 대안을 생각할 수 있습니까?

어떤 접근법을 취해야하며 그 이유는 무엇입니까?

참고 : 이것은 이전 버전에 게시질문 의 수정 된 버전입니다 .


2
수십 개의 다른 권한으로 하나 이상의 bigint필드 (각각 64 비트에 적합) 또는 비트 문자열을 선택할 수 있습니다 . 나는 도움이 될만한 몇 가지 관련 답변을 SO에
Erwin Brandstetter

답변:


7

데이터베이스 보안 자체 에 대해 묻지는 않지만 데이터베이스 보안을 사용하여 원하는 작업을 수행 할 수 있다는 것을 알고 있습니다. 웹앱에서도 사용할 수 있습니다. 데이터베이스 보안을 사용하지 않으려는 경우 스키마가 여전히 적용됩니다.

열 수준 보안, 행 수준 보안 및 계층 적 역할 관리가 필요합니다. 역할 기반 보안은 사용자 기반 보안보다 관리하기가 훨씬 쉽습니다.

이 예제 코드는 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
);

소유자 열이 current_user가되도록하는 삽입 및 업데이트를 위해 data.thing에 트리거를 설정하십시오. 아마도 소유자 만 자신의 레코드 (다른 트리거)를 삭제할 수 있습니다.

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

data.thing에 대한 삽입시 트리거를 설정하여 기본 행 권한을 security.thing_acl에 복사하십시오.

  • 테이블 수준 보안을 적절하게 조정하십시오 (원치 않는 사용자의 삽입 방지). 아무도 데이터 나 보안 스키마를 읽을 수 없어야합니다.
  • 열 수준 보안을 적절하게 조정합니다 (일부 사용자가 열을 보거나 편집하지 못하도록 방지). has_column_privilege ()를 사용하여 사용자가 열을 볼 수 있는지 확인할 수 있습니다.
  • 아마도 보안 정의 자 태그가 필요합니다.
  • 권한을 부여한 사람과 권한 부여자가 해당 행에 대한 권한을 관리 할 수 ​​있는지 여부를 추적하려면 acl 테이블에 grantoradmin_option열을 추가하십시오 .
  • 시험 로트

†이 경우 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

어쨌든 users 테이블의 모든 사용자 행에 대해 별도의 데이터베이스 역할이 있어야하는 것은 과도하고 관리하기 어려운 것으로 보입니다. 그러나 직원의 권한 만 구현하는 개발자에게는 귀하의 답변이 가치 있다고 생각합니다.
JohnCand

1
@JohnCand 다른 곳에서 권한을 관리하는 것이 더 쉬운 방법은 알지 못하지만 일단 발견하면 솔루션을 알려주십시오! :)
Neil McGuigan

4

액세스 제어 목록 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'

ACL 사용은 비즈니스 로직 권한을 처리하는 매우 유연한 방법입니다. 또한 매우 빠릅니다. 평균 오버 헤드는 레코드를 읽는 데 필요한 시간의 25 %에 불과합니다. 유일한 제한 사항은 오브젝트 유형 당 최대 16 개의 사용자 정의 권한을 지원한다는 것입니다.


1

이것을 인코딩 할 다른 가능성, 관계형을 생각할 수 있습니다.

당신이 필요하지 않은 경우 permission_per_itemtableyou 그것을 건너 뛰고 연결할 수 PermissionsItems받는 직접 item_per_user_permissions테이블.

여기에 이미지 설명을 입력하십시오

범례
다이어그램

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.