# 메뉴 기반 권한 시스템 가이드 (동적 화면 대응) ## 개요 **기존 메뉴 기반 권한 시스템을 유지**하면서 **동적으로 생성되는 화면에도 대응**하는 개선된 시스템입니다. ### 핵심 아이디어 💡 ``` 사용자가 화면 생성 ↓ 자동으로 메뉴 추가 (menu_info) ↓ 권한 관리자가 메뉴 권한 설정 (rel_menu_auth) ↓ 사용자는 "메뉴"로만 권한 확인 (직관적!) ``` --- ## 시스템 구조 ### 1. `menu_info` (메뉴 정보) | 컬럼 | 타입 | 설명 | | ---------------- | ------------ | ------------------------------------------------------------------ | | objid | INTEGER | 메뉴 ID (PK) | | menu_name | VARCHAR(100) | 메뉴 이름 | | menu_code | VARCHAR(50) | 메뉴 코드 | | menu_url | VARCHAR(255) | 메뉴 URL | | **menu_type** | VARCHAR(20) | **'static'**(고정 메뉴) 또는 **'dynamic'**(화면 생성 시 자동 추가) | | **screen_code** | VARCHAR(50) | 동적 메뉴인 경우 `screen_definitions.screen_code` | | **company_code** | VARCHAR(20) | 회사 코드 (회사별 메뉴 격리) | | parent_objid | INTEGER | 부모 메뉴 ID (계층 구조) | | is_active | BOOLEAN | 활성/비활성 | ### 2. `rel_menu_auth` (메뉴별 권한) | 컬럼 | 타입 | 설명 | | -------------- | ------- | ----------------------------------------- | | menu_objid | INTEGER | 메뉴 ID (FK) | | auth_objid | INTEGER | 권한 그룹 ID (FK) | | **create_yn** | CHAR(1) | 생성 권한 ('Y'/'N') | | **read_yn** | CHAR(1) | 읽기 권한 ('Y'/'N') | | **update_yn** | CHAR(1) | 수정 권한 ('Y'/'N') | | **delete_yn** | CHAR(1) | 삭제 권한 ('Y'/'N') | | **execute_yn** | CHAR(1) | 실행 권한 ('Y'/'N') - 플로우 실행, DDL 등 | | **export_yn** | CHAR(1) | 내보내기 권한 ('Y'/'N') | --- ## 자동화 기능 🤖 ### 1. 화면 생성 시 자동 메뉴 추가 ```sql -- 사용자가 화면 생성 INSERT INTO screen_definitions (screen_name, screen_code, company_code, ...) VALUES ('계약 관리', 'SCR_CONTRACT', 'ILSHIN', ...); -- ↓ 트리거가 자동 실행 ↓ -- menu_info에 자동 추가됨! -- menu_name = '계약 관리' -- menu_code = 'SCR_CONTRACT' -- menu_url = '/screen/SCR_CONTRACT' -- menu_type = 'dynamic' -- company_code = 'ILSHIN' ``` ### 2. 화면 삭제 시 자동 메뉴 비활성화 ```sql -- 화면 삭제 UPDATE screen_definitions SET is_active = 'D' WHERE screen_code = 'SCR_CONTRACT'; -- ↓ 트리거가 자동 실행 ↓ -- 해당 메뉴도 비활성화됨! UPDATE menu_info SET is_active = FALSE WHERE screen_code = 'SCR_CONTRACT'; ``` --- ## 사용 예시 ### 예시 1: 영업팀에게 계약 관리 화면 읽기 권한 부여 ```sql -- 1. 계약 관리 메뉴 ID 조회 (화면 생성 시 자동으로 추가됨) SELECT objid FROM menu_info WHERE menu_code = 'SCR_CONTRACT'; -- 결과: objid = 1005 -- 2. 영업팀 권한 그룹 ID 조회 SELECT objid FROM authority_master WHERE auth_code = 'SALES_TEAM' AND company_code = 'ILSHIN'; -- 결과: objid = 1001 -- 3. 읽기 권한 부여 INSERT INTO rel_menu_auth (menu_objid, auth_objid, create_yn, read_yn, update_yn, delete_yn, writer) VALUES (1005, 1001, 'N', 'Y', 'N', 'N', 'admin'); ``` ### 예시 2: 개발팀에게 플로우 관리 전체 권한 부여 ```sql -- 플로우 관리 메뉴에 CRUD + 실행 권한 INSERT INTO rel_menu_auth (menu_objid, auth_objid, create_yn, read_yn, update_yn, delete_yn, execute_yn, writer) VALUES ( (SELECT objid FROM menu_info WHERE menu_code = 'MENU_FLOW_MGMT'), (SELECT objid FROM authority_master WHERE auth_code = 'DEV_TEAM'), 'Y', 'Y', 'Y', 'Y', 'Y', 'admin' ); ``` ### 예시 3: 권한 확인 ```sql -- 'john.doe' 사용자가 계약 관리 메뉴를 읽을 수 있는지 확인 SELECT check_user_menu_permission('john.doe', 1005, 'read'); -- 결과: TRUE 또는 FALSE -- 'john.doe' 사용자가 접근 가능한 모든 메뉴 조회 SELECT * FROM get_user_accessible_menus('john.doe', 'ILSHIN'); ``` --- ## 프론트엔드 통합 ### React Hook ```typescript // hooks/useMenuPermission.ts import { useState, useEffect } from "react"; import { checkMenuPermission } from "@/lib/api/menu"; export function useMenuPermission( menuObjid: number, permissionType: "create" | "read" | "update" | "delete" | "execute" | "export" ) { const [hasPermission, setHasPermission] = useState(false); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const checkPermission = async () => { try { const response = await checkMenuPermission(menuObjid, permissionType); setHasPermission(response.success && response.data?.hasPermission); } catch (error) { console.error("권한 확인 오류:", error); setHasPermission(false); } finally { setIsLoading(false); } }; checkPermission(); }, [menuObjid, permissionType]); return { hasPermission, isLoading }; } ``` ### 사용자 메뉴 렌더링 ```tsx // components/Navigation.tsx import { useEffect, useState } from "react"; import { getUserAccessibleMenus } from "@/lib/api/menu"; import { useAuth } from "@/hooks/useAuth"; export function Navigation() { const { user } = useAuth(); const [menus, setMenus] = useState([]); useEffect(() => { const loadMenus = async () => { if (!user) return; const response = await getUserAccessibleMenus( user.userId, user.companyCode ); if (response.success) { setMenus(response.data); } }; loadMenus(); }, [user]); return ( ); } ``` ### 버튼 권한 제어 ```tsx // components/ContractDetail.tsx import { useMenuPermission } from "@/hooks/useMenuPermission"; export function ContractDetail({ menuObjid }: { menuObjid: number }) { const { hasPermission: canUpdate } = useMenuPermission(menuObjid, "update"); const { hasPermission: canDelete } = useMenuPermission(menuObjid, "delete"); return (