ERP-node/docs/권한_체계_가이드.md

590 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`