# 동적 테이블 접근 시스템 개선 완료 > **작성일**: 2025-01-04 > **목적**: 화이트리스트 제거 및 동적 테이블 접근 시스템 구축 --- ## 문제 상황 ### 기존 시스템의 문제점 ```typescript // ❌ 기존 방식: 하드코딩된 화이트리스트 const ALLOWED_TABLES = [ "company_mng", "user_info", "dept_info", "item_info", // 매번 수동으로 추가해야 함! // ... 계속 추가해야 함 ]; // 문제: // 1. 새 테이블 생성 시마다 코드 수정 필요 // 2. 동적 테이블 생성 기능과 충돌 // 3. 유지보수 어려움 // 4. 확장성 부족 ``` ### 발생한 에러 ``` GET /api/data/item_info?page=1&size=100&userLang=KR -> 400 Bad Request -> 접근이 허용되지 않은 테이블입니다: item_info ``` --- ## 개선된 시스템 ### 1. 블랙리스트 방식으로 전환 ```typescript /** * 접근 금지 테이블 목록 (블랙리스트) * 시스템 중요 테이블 및 보안상 접근 금지할 테이블만 명시 */ const BLOCKED_TABLES = [ "pg_catalog", "pg_statistic", "pg_database", "pg_user", "information_schema", "session_tokens", // 세션 토큰 테이블 "password_history", // 패스워드 이력 ]; // ✅ 장점: // - 금지할 테이블만 명시 (시스템 테이블) // - 비즈니스 테이블은 자유롭게 추가 가능 // - 코드 수정 불필요 ``` ### 2. 테이블명 검증 강화 ```typescript /** * 테이블 이름 검증 정규식 * SQL 인젝션 방지: 영문, 숫자, 언더스코어만 허용 */ const TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/; // 검증 순서: // 1. 정규식으로 형식 검증 (SQL 인젝션 방지) // 2. 블랙리스트 확인 (시스템 테이블 차단) // 3. 테이블 존재 여부 확인 (실제 존재하는 테이블만) ``` ### 3. 자동 회사별 필터링 ```typescript // ✅ company_code 컬럼 자동 감지 if (userCompany && userCompany !== "*") { const hasCompanyCode = await this.checkColumnExists(tableName, "company_code"); if (hasCompanyCode) { whereConditions.push(`company_code = $${paramIndex}`); queryParams.push(userCompany); paramIndex++; console.log(`🏢 회사별 필터링 적용: ${tableName}.company_code = ${userCompany}`); } } // 동작 방식: // - company_code 컬럼이 있으면 자동으로 필터링 적용 // - 최고 관리자(company_code = "*")는 전체 데이터 조회 가능 // - 일반 사용자는 자기 회사 데이터만 조회 ``` ### 4. 공통 검증 메서드 ```typescript /** * 테이블 접근 검증 (공통 메서드) */ private async validateTableAccess( tableName: string ): Promise<{ valid: boolean; error?: ServiceResponse }> { // 1. 테이블명 형식 검증 (SQL 인젝션 방지) if (!TABLE_NAME_REGEX.test(tableName)) { return { valid: false, error: { /* ... */ } }; } // 2. 블랙리스트 검증 if (BLOCKED_TABLES.includes(tableName)) { return { valid: false, error: { /* ... */ } }; } // 3. 테이블 존재 여부 확인 const tableExists = await this.checkTableExists(tableName); if (!tableExists) { return { valid: false, error: { /* ... */ } }; } return { valid: true }; } // 모든 메서드에서 재사용: // - getTableData() // - getTableColumns() // - getRecordDetail() // - createRecord() // - updateRecord() // - deleteRecord() // - getJoinedData() ``` --- ## 개선 효과 ### Before (화이트리스트 방식) ```typescript // 1. item_info 테이블 생성 CREATE TABLE item_info (...); // 2. 백엔드 코드 수정 필요 ❌ const ALLOWED_TABLES = [ // ...기존 테이블들 "item_info", // 수동으로 추가! ]; const COMPANY_FILTERED_TABLES = [ // ...기존 테이블들 "item_info", // 또 추가! ]; // 3. 서버 재시작 필요 // 4. 테스트 ``` ### After (블랙리스트 방식) ```typescript // 1. item_info 테이블 생성 CREATE TABLE item_info ( id SERIAL PRIMARY KEY, company_code VARCHAR(20) NOT NULL, -- 이 컬럼만 있으면 자동 필터링! name VARCHAR(100), ... ); // 2. 코드 수정 불필요 ✅ // 3. 서버 재시작 불필요 ✅ // 4. 즉시 사용 가능 ✅ ``` --- ## 보안 강화 ### 1. SQL 인젝션 방지 ```typescript // ❌ 위험한 테이블명 "user_info; DROP TABLE users; --" -> 정규식 검증 실패 "../../etc/passwd" -> 정규식 검증 실패 "pg_user" -> 블랙리스트 차단 // ✅ 안전한 테이블명 "user_info" -> 통과 "item_info" -> 통과 "order_mng_001" -> 통과 ``` ### 2. 시스템 테이블 보호 ```typescript const BLOCKED_TABLES = [ "pg_catalog", // PostgreSQL 카탈로그 "pg_statistic", // 통계 정보 "pg_database", // 데이터베이스 목록 "pg_user", // 사용자 정보 "information_schema", // 스키마 정보 "session_tokens", // 세션 토큰 "password_history", // 패스워드 이력 ]; ``` ### 3. 멀티테넌시 자동 적용 ```typescript // 테이블에 company_code 컬럼이 있으면 자동으로: // 일반 사용자 (company_code = "COMPANY_A") SELECT * FROM item_info WHERE company_code = 'COMPANY_A'; // 최고 관리자 (company_code = "*") SELECT * FROM item_info; -- 모든 회사 데이터 조회 가능 ``` --- ## 사용 예시 ### 1. 새 테이블 생성 ```sql -- 회사별 데이터 격리가 필요한 테이블 CREATE TABLE product_catalog ( id SERIAL PRIMARY KEY, company_code VARCHAR(20) NOT NULL, -- 자동 필터링 활성화 product_name VARCHAR(100), price DECIMAL(10, 2), created_at TIMESTAMPTZ DEFAULT NOW() ); -- 전역 공통 테이블 (회사별 격리 불필요) CREATE TABLE global_settings ( id SERIAL PRIMARY KEY, setting_key VARCHAR(50), setting_value TEXT ); ``` ### 2. API 호출 ```typescript // 프론트엔드에서 그냥 호출하면 끝! const response = await apiClient.get("/api/data/product_catalog", { params: { page: 1, size: 100 } }); // 백엔드에서 자동으로: // 1. 테이블 존재 확인 ✓ // 2. company_code 컬럼 확인 ✓ // 3. 회사별 필터링 적용 ✓ // 4. 데이터 반환 ✓ ``` ### 3. 동적 테이블 생성 (DDL API 연동) ```typescript // 1. DDL API로 테이블 생성 POST /api/ddl/tables { "tableName": "customer_feedback", "columns": [ { "name": "company_code", "type": "VARCHAR(20)", "nullable": false }, { "name": "feedback_text", "type": "TEXT" }, { "name": "rating", "type": "INTEGER" } ] } // 2. 즉시 데이터 조회 가능 (코드 수정 없음) GET /api/data/customer_feedback ``` --- ## 변경된 파일 ### backend-node/src/services/dataService.ts **변경 사항:** - ❌ 제거: `ALLOWED_TABLES` 화이트리스트 - ❌ 제거: `COMPANY_FILTERED_TABLES` 하드코딩 - ✅ 추가: `BLOCKED_TABLES` 블랙리스트 - ✅ 추가: `TABLE_NAME_REGEX` 정규식 검증 - ✅ 추가: `validateTableAccess()` 공통 검증 메서드 - ✅ 추가: `checkColumnExists()` 컬럼 존재 확인 메서드 - ✅ 개선: 자동 회사별 필터링 로직 --- ## 테스트 체크리스트 ### 기본 기능 - [x] 기존 테이블 조회 정상 작동 - [x] 새로운 테이블 조회 정상 작동 - [x] 존재하지 않는 테이블 접근 시 적절한 에러 - [x] 블랙리스트 테이블 접근 시 차단 ### 보안 - [x] SQL 인젝션 시도 차단 - [x] 시스템 테이블 접근 차단 - [x] 회사별 데이터 격리 정상 작동 - [x] 최고 관리자 전체 데이터 조회 가능 ### 성능 - [x] company_code 컬럼 존재 여부 확인 성능 (캐싱 가능) - [x] 테이블 존재 여부 확인 성능 - [x] 정규식 검증 성능 (충분히 빠름) --- ## 향후 개선 사항 ### 1. 컬럼 존재 여부 캐싱 ```typescript // 성능 최적화: 컬럼 정보 캐싱 private columnCache = new Map>(); private async checkColumnExists( tableName: string, columnName: string ): Promise { // 캐시 확인 if (this.columnCache.has(tableName)) { return this.columnCache.get(tableName)!.has(columnName); } // 테이블의 모든 컬럼 조회 및 캐싱 const columns = await this.getTableColumnsSimple(tableName); const columnSet = new Set(columns.map(c => c.column_name)); this.columnCache.set(tableName, columnSet); return columnSet.has(columnName); } ``` ### 2. 블랙리스트 패턴 매칭 ```typescript // pg_* 형태의 패턴 지원 const BLOCKED_TABLE_PATTERNS = [ /^pg_/, // pg_로 시작하는 모든 테이블 /^information_/, // information_으로 시작 /_password$/, // _password로 끝나는 테이블 ]; ``` ### 3. 테이블별 접근 권한 시스템 ```typescript // 향후: 사용자 역할별 테이블 접근 권한 interface TablePermission { tableName: string; roles: string[]; // ["ADMIN", "USER", "VIEWER"] operations: string[]; // ["read", "write", "delete"] } ``` --- ## 결론 ✅ **동적 테이블 접근 시스템 구축 완료** - 화이트리스트 제거로 유지보수 부담 해소 - 블랙리스트 방식으로 보안 유지 - 자동 회사별 필터링으로 멀티테넌시 보장 - 새 테이블 추가 시 코드 수정 불필요 **이제 테이블을 만들 때마다 코드를 수정할 필요가 없습니다!**