# 리소스 기반 권한 시스템 가이드 ## 개요 동적으로 화면과 테이블을 생성하는 Low-Code 플랫폼에 맞춘 **리소스 기반 권한 시스템**입니다. 전통적인 "메뉴" 개념 대신, **"리소스 타입"**(화면, 테이블, 플로우 등)에 대한 **세밀한 CRUD 권한**을 관리합니다. ## 왜 메뉴 기반이 아닌가? ### 문제점 - 현재 시스템은 **동적으로 화면(`screen_definitions`)을 생성** - 사용자가 **DDL을 실행하여 테이블을 동적으로 생성** - **메뉴는 고정되어 있지 않음** (사용자가 생성한 화면 = 새로운 "메뉴") ### 해결책 - **리소스 타입** (SCREEN, TABLE, FLOW, DASHBOARD 등) 기반 권한 - **특정 리소스 ID** 또는 **전체 타입**에 대한 권한 부여 - **6가지 세밀한 권한**: Create, Read, Update, Delete, Execute, Export --- ## 시스템 구조 ### 1. 리소스 타입 (`resource_types`) | type_code | type_name | description | | --------- | --------- | ------------------------------ | | SCREEN | 화면 | 동적으로 생성된 화면 | | TABLE | 테이블 | 동적으로 생성된 데이터 테이블 | | FLOW | 플로우 | 데이터 플로우 | | DASHBOARD | 대시보드 | 대시보드 | | REPORT | 리포트 | 리포트 | | API | API | 외부 API 호출 | | FILE | 파일 | 파일 업로드/다운로드 | | SYSTEM | 시스템 | 시스템 설정 (SUPER_ADMIN 전용) | ### 2. 권한 그룹 (`authority_master`) 기존 테이블 활용 (회사별 격리 지원): - `objid`: 권한 그룹 ID - `auth_name`: 권한 그룹 이름 (예: "영업팀", "개발팀") - `auth_code`: 권한 그룹 코드 - `company_code`: 회사 코드 - `status`: 활성/비활성 ### 3. 리소스별 권한 (`resource_permissions`) | 컬럼 | 타입 | 설명 | | ------------- | ------------ | --------------------------------- | | role_group_id | INTEGER | 권한 그룹 ID (FK) | | resource_type | VARCHAR(50) | 리소스 타입 (SCREEN, TABLE 등) | | resource_id | VARCHAR(255) | 특정 리소스 ID (**NULL = 전체**) | | can_create | BOOLEAN | 생성 권한 | | can_read | BOOLEAN | 읽기 권한 | | can_update | BOOLEAN | 수정 권한 | | can_delete | BOOLEAN | 삭제 권한 | | can_execute | BOOLEAN | 실행 권한 (플로우 실행, DDL 실행) | | can_export | BOOLEAN | 내보내기 권한 | **핵심**: `resource_id`가 **NULL**이면 해당 타입 **전체**에 대한 권한 ### 4. 사용자별 직접 권한 (`user_resource_permissions`) 권한 그룹 외에 **개별 사용자에게 직접 권한** 부여 가능 (보조적 사용) --- ## 권한 체크 로직 ### 우선순위 1. **SUPER_ADMIN** (`company_code = '*'`, `user_type = 'SUPER_ADMIN'`) - 모든 권한 (무조건 TRUE) 2. **COMPANY_ADMIN** (`user_type = 'COMPANY_ADMIN'`) - 자기 회사 모든 리소스 권한 (단, `SYSTEM` 타입 제외) 3. **권한 그룹 기반 권한** (`authority_sub_user` → `resource_permissions`) - 사용자가 속한 권한 그룹의 권한 4. **개별 권한** (`user_resource_permissions`) - 사용자에게 직접 부여된 권한 **최종 판정**: `권한 그룹 권한 OR 개별 권한` (하나라도 TRUE이면 허용) --- ## 사용 예시 ### 예시 1: 영업팀에게 모든 화면 읽기 권한 부여 ```sql -- 1. 영업팀 권한 그룹 ID 조회 SELECT objid FROM authority_master WHERE auth_code = 'SALES_TEAM' AND company_code = 'ILSHIN'; -- 결과: objid = 1001 -- 2. 화면(SCREEN) 전체에 대한 읽기 권한 부여 INSERT INTO resource_permissions (role_group_id, resource_type, resource_id, can_read, created_by) VALUES (1001, 'SCREEN', NULL, TRUE, 'admin'); -- ^^^^ ^^^^ NULL = 모든 화면 ``` ### 예시 2: 특정 화면에만 수정 권한 부여 ```sql -- 특정 화면 ID: 'SCR_SALES_REPORT' (screen_definitions.screen_code) INSERT INTO resource_permissions (role_group_id, resource_type, resource_id, can_read, can_update, created_by) VALUES (1001, 'SCREEN', 'SCR_SALES_REPORT', TRUE, TRUE, 'admin'); -- ^^^^^^^^^^^^^^^^^ 특정 화면만 ``` ### 예시 3: 테이블 CRUD 권한 부여 (삭제 제외) ```sql -- 모든 테이블에 대해 CRU (Create, Read, Update) 권한 부여 INSERT INTO resource_permissions ( role_group_id, resource_type, resource_id, can_create, can_read, can_update, can_delete, created_by ) VALUES (1001, 'TABLE', NULL, TRUE, TRUE, TRUE, FALSE, 'admin'); ``` ### 예시 4: 플로우 실행 권한 부여 ```sql -- 특정 플로우만 실행 가능 INSERT INTO resource_permissions ( role_group_id, resource_type, resource_id, can_read, can_execute, created_by ) VALUES (1001, 'FLOW', '29', TRUE, TRUE, 'admin'); -- ^^ flow_definition.id ``` ### 예시 5: 개별 사용자에게 직접 권한 부여 ```sql -- 'john.doe' 사용자에게 시스템 설정 읽기 권한 INSERT INTO user_resource_permissions ( user_id, resource_type, resource_id, can_read, created_by ) VALUES ('john.doe', 'SYSTEM', NULL, TRUE, 'admin'); ``` --- ## 백엔드 API 사용법 ### 1. 권한 체크 함수 ```sql -- 사용자 'john.doe'가 화면 'SCR_SALES_REPORT'를 읽을 수 있는지 확인 SELECT check_user_resource_permission('john.doe', 'SCREEN', 'SCR_SALES_REPORT', 'read'); -- 결과: TRUE 또는 FALSE -- 테이블 'contract_mgmt'를 삭제할 수 있는지 확인 SELECT check_user_resource_permission('john.doe', 'TABLE', 'contract_mgmt', 'delete'); ``` ### 2. 접근 가능한 리소스 목록 조회 ```sql -- 사용자 'john.doe'가 읽을 수 있는 모든 화면 목록 SELECT * FROM get_user_accessible_resources('john.doe', 'SCREEN', 'read'); -- 결과 예시: -- resource_id | can_create | can_read | can_update | can_delete | can_execute | can_export -- ------------+------------+----------+------------+------------+-------------+----------- -- * | FALSE | TRUE | FALSE | FALSE | FALSE | FALSE -- SCR_SALES | FALSE | TRUE | TRUE | FALSE | FALSE | TRUE ``` --- ## 프론트엔드 통합 ### React Hook 예시 ```typescript // hooks/usePermission.ts import { useState, useEffect } from "react"; import { checkResourcePermission } from "@/lib/api/permission"; export function usePermission( resourceType: string, resourceId: string | null, permissionType: "create" | "read" | "update" | "delete" | "execute" | "export" ) { const [hasPermission, setHasPermission] = useState(false); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const checkPermission = async () => { setIsLoading(true); try { const response = await checkResourcePermission({ resourceType, resourceId, permissionType, }); setHasPermission(response.success && response.data?.hasPermission); } catch (error) { console.error("권한 확인 오류:", error); setHasPermission(false); } finally { setIsLoading(false); } }; checkPermission(); }, [resourceType, resourceId, permissionType]); return { hasPermission, isLoading }; } ``` ### 컴포넌트에서 사용 ```tsx // components/ScreenDetail.tsx import { usePermission } from "@/hooks/usePermission"; import { Button } from "@/components/ui/button"; export function ScreenDetail({ screenCode }: { screenCode: string }) { const { hasPermission: canUpdate } = usePermission( "SCREEN", screenCode, "update" ); const { hasPermission: canDelete } = usePermission( "SCREEN", screenCode, "delete" ); return (