590 lines
16 KiB
Markdown
590 lines
16 KiB
Markdown
|
|
# 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 (
|
|||
|
|
<div>
|
|||
|
|
{/* 슈퍼관리자만 표시 */}
|
|||
|
|
{user?.isSuperAdmin && (
|
|||
|
|
<Button onClick={handleDDLExecution}>DDL 실행</Button>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 관리자만 표시 (슈퍼관리자 + 회사관리자) */}
|
|||
|
|
{user?.isAdmin && (
|
|||
|
|
<Button onClick={handleUserManagement}>사용자 관리</Button>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 모든 사용자 표시 */}
|
|||
|
|
<Button onClick={handleDataView}>데이터 조회</Button>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 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 <div>접근 권한이 없습니다.</div>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return <Table data={data} />;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 실무 예제
|
|||
|
|
|
|||
|
|
### 예제 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 (
|
|||
|
|
<div>
|
|||
|
|
{showCompanySelector && (
|
|||
|
|
<Select value={selectedCompany} onChange={setSelectedCompany}>
|
|||
|
|
<option value="*">전체 회사</option>
|
|||
|
|
<option value="20">회사 20</option>
|
|||
|
|
<option value="30">회사 30</option>
|
|||
|
|
</Select>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<OrderTable companyCode={selectedCompany} />
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 예제 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 (
|
|||
|
|
<div>
|
|||
|
|
<h2>DDL 실행 (슈퍼관리자 전용)</h2>
|
|||
|
|
<textarea placeholder="SQL 입력" />
|
|||
|
|
<Button onClick={handleExecute}>실행</Button>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## FAQ
|
|||
|
|
|
|||
|
|
### Q1: 기존 ADMIN 계정은 어떻게 되나요?
|
|||
|
|
|
|||
|
|
**A:** 마이그레이션 스크립트가 자동으로 처리합니다:
|
|||
|
|
|
|||
|
|
- `company_code = '*'`인 ADMIN → `SUPER_ADMIN`으로 변경
|
|||
|
|
- `company_code = '특정코드'`인 ADMIN → `COMPANY_ADMIN`으로 변경
|
|||
|
|
|
|||
|
|
### Q2: 슈퍼관리자 계정은 몇 개가 적절한가요?
|
|||
|
|
|
|||
|
|
**A:** 보안상 최소 1개, 최대 2-3개를 권장합니다. 모든 DDL 실행이 감사 로그에 기록되므로 책임 추적이 가능합니다.
|
|||
|
|
|
|||
|
|
### Q3: 회사 관리자가 다른 회사 데이터를 조회하려면?
|
|||
|
|
|
|||
|
|
**A:** 불가능합니다. 회사 간 데이터 격리가 필수입니다. 필요시 슈퍼관리자에게 요청하거나, API 통합 기능을 사용해야 합니다.
|
|||
|
|
|
|||
|
|
### Q4: USER가 COMPANY_ADMIN으로 승격하려면?
|
|||
|
|
|
|||
|
|
**A:**
|
|||
|
|
|
|||
|
|
1. 슈퍼관리자 또는 해당 회사의 관리자가 처리
|
|||
|
|
2. `UPDATE user_info SET user_type = 'COMPANY_ADMIN' WHERE user_id = 'xxx'`
|
|||
|
|
3. 사용자 재로그인 필요
|
|||
|
|
|
|||
|
|
### Q5: 회사 코드 '\*'의 의미는?
|
|||
|
|
|
|||
|
|
**A:** 와일드카드로, "모든 회사"를 의미합니다. 슈퍼관리자 전용 코드이며, 일반 회사 코드로는 사용할 수 없습니다.
|
|||
|
|
|
|||
|
|
### Q6: 권한 체크는 어디서 해야 하나요?
|
|||
|
|
|
|||
|
|
**A:**
|
|||
|
|
|
|||
|
|
- **백엔드 (필수)**: 미들웨어 + 서비스 레이어 모두
|
|||
|
|
- **프론트엔드 (선택)**: UI 렌더링 최적화용 (보안 목적 아님)
|
|||
|
|
|
|||
|
|
### Q7: 테이블에 회사 필터링을 추가하려면?
|
|||
|
|
|
|||
|
|
**A:**
|
|||
|
|
|
|||
|
|
1. 테이블에 `company_code` 컬럼 추가
|
|||
|
|
2. `backend-node/src/services/dataService.ts`의 `COMPANY_FILTERED_TABLES` 배열에 테이블명 추가
|
|||
|
|
3. 자동으로 회사 필터링 적용됨
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 체크리스트
|
|||
|
|
|
|||
|
|
### 새로운 엔드포인트 추가 시
|
|||
|
|
|
|||
|
|
- [ ] 적절한 권한 미들웨어 적용 (`requireSuperAdmin`, `requireAdmin` 등)
|
|||
|
|
- [ ] 서비스 레이어에서 `canAccessCompanyData()` 체크
|
|||
|
|
- [ ] 감사 로그 기록 (중요 작업의 경우)
|
|||
|
|
- [ ] 프론트엔드 UI에 권한 기반 렌더링 적용
|
|||
|
|
- [ ] 에러 메시지에 필요한 권한 레벨 명시
|
|||
|
|
|
|||
|
|
### 새로운 테이블 생성 시
|
|||
|
|
|
|||
|
|
- [ ] `company_code` 컬럼 추가 (회사별 데이터인 경우)
|
|||
|
|
- [ ] `COMPANY_FILTERED_TABLES` 배열에 등록
|
|||
|
|
- [ ] 인덱스 생성: `CREATE INDEX ON table_name(company_code)`
|
|||
|
|
- [ ] Row Level Security 정책 고려 (선택사항)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 참고 파일
|
|||
|
|
|
|||
|
|
- 마이그레이션: `/db/migrations/026_add_user_type_hierarchy.sql`
|
|||
|
|
- 권한 유틸: `/backend-node/src/utils/permissionUtils.ts`
|
|||
|
|
- 미들웨어: `/backend-node/src/middleware/permissionMiddleware.ts`
|
|||
|
|
- 타입 정의: `/backend-node/src/types/auth.ts`
|
|||
|
|
- 인증 서비스: `/backend-node/src/services/authService.ts`
|