# 동적 테이블 접근 시스템 개선 완료

> **작성일**: 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<any> }> {
  // 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<string, Set<string>>();

private async checkColumnExists(
  tableName: string,
  columnName: string
): Promise<boolean> {
  // 캐시 확인
  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"]
}
```

---

## 결론

✅ **동적 테이블 접근 시스템 구축 완료**

- 화이트리스트 제거로 유지보수 부담 해소
- 블랙리스트 방식으로 보안 유지
- 자동 회사별 필터링으로 멀티테넌시 보장
- 새 테이블 추가 시 코드 수정 불필요

**이제 테이블을 만들 때마다 코드를 수정할 필요가 없습니다!**





