# 3단계 권한 체계 가이드 ## 📋 목차 1. [권한 체계 개요](#권한-체계-개요) 2. [권한 레벨 상세](#권한-레벨-상세) 3. [데이터베이스 설정](#데이터베이스-설정) 4. [백엔드 구현](#백엔드-구현) 5. [프론트엔드 구현](#프론트엔드-구현) 6. [실무 예제](#실무-예제) 7. [FAQ](#faq) --- ## 권한 체계 개요 ### 3단계 권한 구조 ``` ┌────────────────────┬──────────────┬─────────────────┬────────────────────────┐ │ 권한 레벨 │ company_code │ user_type │ 접근 범위 │ ├────────────────────┼──────────────┼─────────────────┼────────────────────────┤ │ 최고 관리자 │ * │ SUPER_ADMIN │ ✅ 전체 회사 데이터 │ │ (Super Admin) │ │ │ ✅ DDL 실행 권한 │ │ │ │ │ ✅ 회사 생성/삭제 │ │ │ │ │ ✅ 시스템 설정 │ ├────────────────────┼──────────────┼─────────────────┼────────────────────────┤ │ 회사 관리자 │ 20 │ COMPANY_ADMIN │ ✅ 자기 회사 데이터 │ │ (Company Admin) │ │ │ ✅ 회사 사용자 관리 │ │ │ │ │ ✅ 회사 설정 변경 │ │ │ │ │ ❌ DDL 실행 불가 │ │ │ │ │ ❌ 타회사 접근 불가 │ ├────────────────────┼──────────────┼─────────────────┼────────────────────────┤ │ 일반 사용자 │ 20 │ USER │ ✅ 자기 회사 데이터 │ │ (User) │ │ │ ❌ 사용자 관리 불가 │ │ │ │ │ ❌ 설정 변경 불가 │ └────────────────────┴──────────────┴─────────────────┴────────────────────────┘ ``` ### 핵심 원칙 1. **company_code = "\*"** → 전체 시스템 접근 (슈퍼관리자 전용) 2. **company_code = "특정코드"** → 해당 회사만 접근 3. **user_type** → 회사 내 권한 레벨 결정 --- ## 권한 레벨 상세 ### 1️⃣ 슈퍼관리자 (SUPER_ADMIN) **조건:** - `company_code = '*'` - `user_type = 'SUPER_ADMIN'` **권한:** - ✅ 모든 회사 데이터 조회/수정 - ✅ DDL 실행 (CREATE TABLE, ALTER TABLE 등) - ✅ 회사 생성/삭제 - ✅ 시스템 설정 변경 - ✅ 모든 사용자 관리 - ✅ 코드 관리, 템플릿 관리 등 전역 설정 **사용 사례:** - 시스템 전체 관리자 - 데이터베이스 스키마 변경 - 새로운 회사 추가 - 전사 공통 설정 관리 **계정 예시:** ```sql INSERT INTO user_info (user_id, user_name, company_code, user_type) VALUES ('super_admin', '시스템 관리자', '*', 'SUPER_ADMIN'); ``` --- ### 2️⃣ 회사 관리자 (COMPANY_ADMIN) **조건:** - `company_code = '특정 회사 코드'` (예: '20') - `user_type = 'COMPANY_ADMIN'` **권한:** - ✅ 자기 회사 데이터 조회/수정 - ✅ 자기 회사 사용자 관리 (추가/수정/삭제) - ✅ 자기 회사 설정 변경 - ✅ 자기 회사 대시보드/화면 관리 - ❌ DDL 실행 불가 - ❌ 타 회사 데이터 접근 불가 - ❌ 시스템 전역 설정 변경 불가 **사용 사례:** - 각 회사의 IT 관리자 - 회사 내 사용자 계정 관리 - 회사별 커스터마이징 설정 **계정 예시:** ```sql INSERT INTO user_info (user_id, user_name, company_code, user_type) VALUES ('company_admin_20', '회사20 관리자', '20', 'COMPANY_ADMIN'); ``` --- ### 3️⃣ 일반 사용자 (USER) **조건:** - `company_code = '특정 회사 코드'` (예: '20') - `user_type = 'USER'` **권한:** - ✅ 자기 회사 데이터 조회/수정 - ✅ 자신이 만든 화면/대시보드 관리 - ❌ 사용자 관리 불가 - ❌ 회사 설정 변경 불가 - ❌ 타 회사 데이터 접근 불가 **사용 사례:** - 일반 업무 사용자 - 데이터 입력/조회 - 개인 대시보드 생성 **계정 예시:** ```sql INSERT INTO user_info (user_id, user_name, company_code, user_type) VALUES ('user_kim', '김철수', '20', 'USER'); ``` --- ## 데이터베이스 설정 ### 마이그레이션 실행 ```bash # 권한 체계 마이그레이션 실행 psql -U postgres -d your_database -f db/migrations/026_add_user_type_hierarchy.sql ``` ### 주요 변경사항 1. **코드 테이블 업데이트:** - `ADMIN` → `COMPANY_ADMIN` 으로 변경 - `SUPER_ADMIN` 신규 추가 2. **PostgreSQL 함수 추가:** - `is_super_admin(user_id)` - 슈퍼관리자 확인 - `is_company_admin(user_id, company_code)` - 회사 관리자 확인 - `can_access_company_data(user_id, company_code)` - 데이터 접근 권한 3. **권한 뷰 생성:** - `v_user_permissions` - 사용자별 권한 요약 --- ## 백엔드 구현 ### 1. 권한 체크 유틸리티 사용 ```typescript import { isSuperAdmin, isCompanyAdmin, isAdmin, canExecuteDDL, canAccessCompanyData, canManageUsers, } from "../utils/permissionUtils"; // 슈퍼관리자 확인 if (isSuperAdmin(req.user)) { // 전체 데이터 조회 } // 회사 데이터 접근 권한 확인 if (canAccessCompanyData(req.user, targetCompanyCode)) { // 해당 회사 데이터 조회 } // 사용자 관리 권한 확인 if (canManageUsers(req.user, targetCompanyCode)) { // 사용자 추가/수정/삭제 } ``` ### 2. 미들웨어 사용 ```typescript import { requireSuperAdmin, requireAdmin, requireCompanyAccess, requireUserManagement, requireDDLPermission, } from "../middleware/permissionMiddleware"; // 슈퍼관리자 전용 엔드포인트 router.post( "/api/admin/ddl/execute", authenticate, requireDDLPermission, ddlController.execute ); // 관리자 전용 엔드포인트 (슈퍼관리자 + 회사관리자) router.get( "/api/admin/users", authenticate, requireAdmin, userController.getUserList ); // 회사 데이터 접근 체크 router.get( "/api/data/:companyCode/orders", authenticate, requireCompanyAccess, orderController.getOrders ); // 사용자 관리 권한 체크 router.post( "/api/admin/users/:companyCode", authenticate, requireUserManagement, userController.createUser ); ``` ### 3. 서비스 레이어 구현 ```typescript // ❌ 잘못된 방법 - 하드코딩된 회사 코드 async getOrders(companyCode: string) { return query("SELECT * FROM orders WHERE company_code = $1", [companyCode]); } // ✅ 올바른 방법 - 권한 체크 포함 async getOrders(user: PersonBean, companyCode: string) { // 권한 확인 if (!canAccessCompanyData(user, companyCode)) { throw new Error("해당 회사 데이터에 접근할 권한이 없습니다."); } // 슈퍼관리자는 모든 데이터 조회 가능 if (isSuperAdmin(user)) { if (companyCode === "*") { return query("SELECT * FROM orders"); // 전체 조회 } } // 일반 사용자/회사 관리자는 자기 회사만 return query("SELECT * FROM orders WHERE company_code = $1", [companyCode]); } ``` --- ## 프론트엔드 구현 ### 1. 사용자 타입 정의 ```typescript // frontend/types/user.ts export interface UserInfo { userId: string; userName: string; companyCode: string; userType: string; // 'SUPER_ADMIN' | 'COMPANY_ADMIN' | 'USER' isSuperAdmin?: boolean; isCompanyAdmin?: boolean; isAdmin?: boolean; } ``` ### 2. 권한 기반 UI 렌더링 ```tsx import { useAuth } from "@/hooks/useAuth"; function AdminPanel() { const { user } = useAuth(); return (
{/* 슈퍼관리자만 표시 */} {user?.isSuperAdmin && ( )} {/* 관리자만 표시 (슈퍼관리자 + 회사관리자) */} {user?.isAdmin && ( )} {/* 모든 사용자 표시 */}
); } ``` ### 3. 권한 체크 Hook ```typescript // frontend/hooks/usePermissions.ts export function usePermissions() { const { user } = useAuth(); return { isSuperAdmin: user?.isSuperAdmin ?? false, isCompanyAdmin: user?.isCompanyAdmin ?? false, isAdmin: user?.isAdmin ?? false, canExecuteDDL: user?.isSuperAdmin ?? false, canManageUsers: user?.isAdmin ?? false, canAccessCompany: (companyCode: string) => { if (user?.isSuperAdmin) return true; return user?.companyCode === companyCode; }, }; } // 사용 예시 function DataTable({ companyCode }: { companyCode: string }) { const { canAccessCompany } = usePermissions(); if (!canAccessCompany(companyCode)) { return
접근 권한이 없습니다.
; } return ; } ``` --- ## 실무 예제 ### 예제 1: 주문 데이터 조회 **시나리오:** - 슈퍼관리자: 모든 회사의 주문 조회 - 회사20 관리자: 회사20의 주문만 조회 - 회사20 사용자: 회사20의 주문만 조회 **백엔드 구현:** ```typescript // orders.service.ts export class OrderService { async getOrders(user: PersonBean, companyCode?: string) { let sql = "SELECT * FROM orders WHERE 1=1"; const params: any[] = []; // 슈퍼관리자가 아닌 경우 회사 필터 적용 if (!isSuperAdmin(user)) { sql += " AND company_code = $1"; params.push(user.companyCode); } else if (companyCode && companyCode !== "*") { // 슈퍼관리자가 특정 회사를 지정한 경우 sql += " AND company_code = $1"; params.push(companyCode); } return query(sql, params); } } ``` **프론트엔드 구현:** ```tsx function OrderList() { const { user } = useAuth(); const [selectedCompany, setSelectedCompany] = useState(user?.companyCode); // 슈퍼관리자는 회사 선택 가능 const showCompanySelector = user?.isSuperAdmin; return (
{showCompanySelector && ( )}
); } ``` --- ### 예제 2: 사용자 관리 **시나리오:** - 슈퍼관리자: 모든 회사의 사용자 관리 - 회사20 관리자: 회사20 사용자만 관리 - 회사20 사용자: 사용자 관리 불가 **백엔드 구현:** ```typescript // users.controller.ts router.post("/api/admin/users", authenticate, async (req, res) => { const { companyCode, userId, userName } = req.body; // 권한 확인 if (!canManageUsers(req.user, companyCode)) { return res.status(403).json({ success: false, error: "사용자 관리 권한이 없습니다.", }); } // 슈퍼관리자가 아닌 경우, 자기 회사만 가능 if (!isSuperAdmin(req.user) && companyCode !== req.user.companyCode) { return res.status(403).json({ success: false, error: "다른 회사의 사용자를 생성할 수 없습니다.", }); } // 사용자 생성 await UserService.createUser({ companyCode, userId, userName }); res.json({ success: true }); }); ``` --- ### 예제 3: DDL 실행 (테이블 생성) **시나리오:** - 슈퍼관리자만 DDL 실행 가능 - 다른 모든 사용자는 차단 **백엔드 구현:** ```typescript // ddl.controller.ts router.post( "/api/admin/ddl/execute", authenticate, requireDDLPermission, // 슈퍼관리자 체크 미들웨어 async (req, res) => { const { sql } = req.body; // 추가 보안 검증 if (!canExecuteDDL(req.user)) { return res.status(403).json({ success: false, error: "DDL 실행 권한이 없습니다.", }); } // DDL 실행 await query(sql); // 감사 로그 기록 await AuditService.logDDL({ userId: req.user.userId, sql, timestamp: new Date(), }); res.json({ success: true }); } ); ``` **프론트엔드 구현:** ```tsx function DDLExecutor() { const { user } = useAuth(); // 슈퍼관리자가 아니면 컴포넌트 자체를 숨김 if (!user?.isSuperAdmin) { return null; } return (

DDL 실행 (슈퍼관리자 전용)