247
docs/design/424-spring-boot-vpd-backoffice/README.md
Normal file
247
docs/design/424-spring-boot-vpd-backoffice/README.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# 설계서: Spring Boot VPD/ORDS 권한 백오피스 (#424)
|
||||
|
||||
> **상태**: Draft
|
||||
> **작성**: [AI] Architect · **최종수정**: 2026-06-23
|
||||
> **추적성** — Redmine: #424 · 관련 ADR: 없음
|
||||
> · 구현 파일: `pom.xml`, `src/main/**`, `src/test/**` · 테스트: `mvn test`
|
||||
|
||||
## 1. 목적 (Why)
|
||||
|
||||
운영자와 데모 담당자가 ADB VPD/Redaction 권한 매핑, Bearer Token, ORDS 호출 결과를 한 화면에서 관리하고 검증할 수 있는 Spring Boot 백오피스를 만든다.
|
||||
|
||||
## 2. 범위 (Scope)
|
||||
|
||||
- **포함**:
|
||||
- Spring Boot 3.x, Java 21 또는 17, HikariCP, MyBatis 기반 백오피스 골격
|
||||
- ADB 권한 관리 테이블과 ORDS Bearer Key 검증 테이블을 조회/수정하는 서비스
|
||||
- 보호 객체, 사용자, 역할, 행 규칙, 컬럼 표시 권한, Bearer Token 관리 화면
|
||||
- Bearer Token을 사용해 ORDS에 `SELECT *` 성격의 조회를 실행하고 VPD/Redaction 적용 결과를 확인하는 화면
|
||||
- Thymeleaf + HTMX + Alpine.js + Bootstrap 5 기반 서버 렌더링 프론트
|
||||
- Audit log 저장과 오류 유형 구분 표시
|
||||
- **제외 (out of scope)**:
|
||||
- 외부 SSO 연동
|
||||
- 운영 승인 워크플로우
|
||||
- DDS DATA GRANT 직접 생성 UI
|
||||
- 원격 PostgreSQL/MySQL 데이터 생성 자동화
|
||||
- 운영 배포 파이프라인
|
||||
|
||||
## 3. 인수조건 (Acceptance Criteria)
|
||||
|
||||
- [ ] Spring Boot 애플리케이션이 HikariCP와 MyBatis로 ADB에 연결된다.
|
||||
- [ ] 보호 객체 목록은 DB에 등록된 whitelist에서만 선택 가능하며, 자유 입력 객체명으로 SQL 또는 ORDS 호출을 만들 수 없다.
|
||||
- [ ] 내부 사용자, 역할, 사용자-역할, 객체 권한, 행 규칙, 컬럼 표시 권한을 백오피스에서 조회하고 저장할 수 있다.
|
||||
- [ ] Bearer Token은 발급 시 원문을 한 번만 보여주고 DB에는 hash, prefix, 만료/회수 상태만 저장한다.
|
||||
- [ ] ORDS 호출 검증 화면에서 Bearer Token과 보호 객체를 선택하면 ORDS 호출 결과, 반환 컬럼, 행 수, 오류 유형을 확인할 수 있다.
|
||||
- [ ] 권한 없음, 잘못된 토큰, 객체 권한 없음, VPD 0건, Redaction NULL 표시를 서로 구분해 보여준다.
|
||||
- [ ] 권한 변경, 토큰 발급/회수, ORDS 검증 호출은 audit log에 남는다.
|
||||
- [ ] `mvn test`로 핵심 서비스 단위 테스트와 Mapper/SQL 검증 테스트를 실행할 수 있다.
|
||||
|
||||
## 4. 컨텍스트 & 제약
|
||||
|
||||
- 의존성:
|
||||
- Oracle Autonomous Database
|
||||
- ORDS Handler 또는 REST Enabled SQL 성격의 조회 API
|
||||
- Oracle JDBC, MyBatis, Spring Security, Thymeleaf, HTMX
|
||||
- 제약:
|
||||
- `.env`와 DB 비밀번호, Bearer Token 원문은 git에 저장하지 않는다.
|
||||
- 객체명은 반드시 DB에 등록된 보호 객체 whitelist로 제한한다.
|
||||
- 백오피스 DB 계정은 권한 관리 테이블과 검증용 ORDS 호출에 필요한 최소 권한만 갖는다.
|
||||
- VPD는 행(row) 통제, Redaction은 컬럼 값 마스킹/NULL 처리로 설명하고 구현한다.
|
||||
- 가정:
|
||||
- 기존 SQL의 `CB_*` ORDS/VPD Bearer Key 예제를 백오피스 데이터 모델의 기준으로 삼는다.
|
||||
- 첫 구현은 단일 관리자 로그인 또는 개발용 in-memory user로 시작하고, 운영 SSO는 후속 범위로 둔다.
|
||||
- ORDS 호출은 백오피스 서버에서 수행하며 브라우저에는 Bearer Token 원문을 보존하지 않는다.
|
||||
|
||||
## 5. 아키텍처 개요
|
||||
|
||||
### 모듈/파일 구조
|
||||
|
||||
```text
|
||||
pom.xml
|
||||
src/main/java/com/cloudhandson/vpdbackoffice/
|
||||
VpdBackofficeApplication.java
|
||||
config/
|
||||
DataSourceConfig.java
|
||||
SecurityConfig.java
|
||||
OrdsClientConfig.java
|
||||
domain/
|
||||
user/
|
||||
permission/
|
||||
token/
|
||||
protectedobject/
|
||||
probe/
|
||||
audit/
|
||||
mapper/
|
||||
UserMapper.java
|
||||
PermissionMapper.java
|
||||
BearerTokenMapper.java
|
||||
ProtectedObjectMapper.java
|
||||
AuditMapper.java
|
||||
service/
|
||||
PermissionService.java
|
||||
BearerTokenService.java
|
||||
OrdsProbeService.java
|
||||
AuditService.java
|
||||
web/
|
||||
PermissionController.java
|
||||
TokenController.java
|
||||
ProbeController.java
|
||||
DashboardController.java
|
||||
src/main/resources/
|
||||
application.yml
|
||||
mapper/*.xml
|
||||
templates/**/*.html
|
||||
static/css/app.css
|
||||
static/js/app.js
|
||||
src/test/java/com/cloudhandson/vpdbackoffice/
|
||||
```
|
||||
|
||||
### 데이터 흐름
|
||||
|
||||
```text
|
||||
관리자 브라우저
|
||||
-> Spring MVC Controller
|
||||
-> Service
|
||||
-> MyBatis Mapper
|
||||
-> ADB 권한/토큰/보호 객체 테이블
|
||||
|
||||
검증 호출
|
||||
-> ProbeController
|
||||
-> OrdsProbeService
|
||||
-> BearerTokenService에서 token 상태 확인
|
||||
-> ProtectedObject whitelist 확인
|
||||
-> ORDS HTTP 호출 Authorization: Bearer <token>
|
||||
-> 응답/오류 분류
|
||||
-> Audit 저장
|
||||
-> Thymeleaf fragment로 결과 영역 갱신
|
||||
```
|
||||
|
||||
### I/O와 순수 로직 경계
|
||||
|
||||
- Controller는 요청 파라미터 검증과 화면 모델 구성만 담당한다.
|
||||
- Service는 트랜잭션, 권한 변경, 토큰 발급/회수, ORDS 호출 오케스트레이션을 담당한다.
|
||||
- Mapper는 SQL I/O만 담당한다.
|
||||
- 순수 로직은 `TokenGenerator`, `TokenHasher`, `ProbeErrorClassifier`, `ObjectNamePolicy`로 분리해 단위 테스트한다.
|
||||
|
||||
## 6. 데이터 모델
|
||||
|
||||
### 백오피스 기준 테이블
|
||||
|
||||
| 테이블 | 역할 | 주요 컬럼 |
|
||||
|---|---|---|
|
||||
| `CB_APP_USER` | 내부 사용자 | `user_id`, `username`, `emp_no`, `dept_code`, `active_yn` |
|
||||
| `CB_APP_ROLE` | 역할 | `role_id`, `role_name`, `description` |
|
||||
| `CB_USER_ROLE` | 사용자-역할 매핑 | `user_id`, `role_id` |
|
||||
| `CB_PROTECTED_OBJECT` | 보호 객체 whitelist | `object_id`, `owner`, `object_name`, `ords_path`, `enabled_yn` |
|
||||
| `CB_PERMISSION` | 객체 접근 권한 | `permission_id`, `role_id`, `object_id`, `action` |
|
||||
| `CB_PERMISSION_RULE` | 행 조건 | `rule_id`, `permission_id`, `rule_type`, `rule_value` |
|
||||
| `CB_PROTECTED_COLUMN` | 컬럼 표시 정책 | `column_id`, `object_id`, `column_name`, `sensitive_yn`, `visible_role_id` |
|
||||
| `CB_AGENT_BEARER_KEY` | Bearer Token 관리 | `key_id`, `user_id`, `key_prefix`, `key_hash`, `expires_at`, `revoked_at` |
|
||||
| `CB_ORDS_PROBE_AUDIT` | ORDS 검증 이력 | `audit_id`, `key_id`, `object_id`, `status`, `row_count`, `error_code`, `created_at` |
|
||||
|
||||
### 경계 검증 규칙
|
||||
|
||||
- `owner`, `object_name`, `column_name`은 Oracle identifier 허용 문자로 검증하되, 검증 후에도 SQL 문자열 직접 조합에 사용하지 않는다.
|
||||
- ORDS 검증 대상은 `CB_PROTECTED_OBJECT.enabled_yn = 'Y'`인 행만 허용한다.
|
||||
- Token 원문은 발급 응답 화면에서만 사용하고 DB, log, audit에는 저장하지 않는다.
|
||||
- `rule_type`은 `ALL`, `MY_DEPT`, `SELF`, `REGION`, `CUSTOM_PREDICATE` 중 허용 값만 저장한다.
|
||||
- `CUSTOM_PREDICATE`는 초기 구현에서 비활성화한다.
|
||||
|
||||
## 7. 함수 명세 (Function Specs)
|
||||
|
||||
| 함수 | 책임(1줄) | 시그니처(잠정) | 입력 | 출력 | 에러/실패 | 복잡? |
|
||||
|------|-----------|----------------|------|------|-----------|-------|
|
||||
| `issueToken` | Bearer Token을 생성하고 hash만 저장한다 | `IssuedToken issueToken(TokenIssueCommand command)` | 사용자, 만료일, 설명 | prefix와 원문 token | 비활성 사용자, 잘못된 만료일 | **복잡** |
|
||||
| `revokeToken` | Bearer Token을 회수한다 | `void revokeToken(long keyId, String reason)` | key id, 사유 | 없음 | 없는 key, 이미 회수됨 | 단순 |
|
||||
| `savePermissionSet` | 역할별 객체 권한과 행/컬럼 규칙을 저장한다 | `PermissionSet savePermissionSet(PermissionSetCommand command)` | 역할, 객체, 규칙 목록 | 저장된 권한 | whitelist 위반, 중복 규칙 | **복잡** |
|
||||
| `runProbe` | ORDS Bearer 호출을 실행하고 결과를 분류한다 | `ProbeResult runProbe(ProbeCommand command)` | token id, object id, limit | 결과 rows, columns, status | 토큰 만료, ORDS 오류, timeout | **복잡** |
|
||||
| `classifyProbeError` | ORDS/Oracle 오류를 화면 표시 상태로 분류한다 | `ProbeStatus classifyProbeError(HttpStatus status, String body)` | HTTP 상태, 응답 body | 분류 상태 | 알 수 없는 오류 | 단순 |
|
||||
| `assertAllowedObject` | 보호 객체 whitelist를 검증한다 | `ProtectedObject assertAllowedObject(long objectId)` | object id | 보호 객체 | 비활성/없는 객체 | 단순 |
|
||||
| `recordAudit` | 관리 작업과 검증 호출을 audit에 기록한다 | `void recordAudit(AuditEvent event)` | 이벤트 | 없음 | DB insert 실패 | 단순 |
|
||||
|
||||
복잡 함수별 상세 설계서는 다음 파일에 둔다.
|
||||
|
||||
- `fn-issueToken.md`
|
||||
- `fn-savePermissionSet.md`
|
||||
- `fn-runProbe.md`
|
||||
|
||||
## 8. 흐름 / 알고리즘
|
||||
|
||||
### 권한 저장
|
||||
|
||||
1. Controller가 역할, 보호 객체, 행 규칙, 컬럼 표시 정책 입력을 받는다.
|
||||
2. Service가 role/object 존재와 활성 상태를 확인한다.
|
||||
3. 행 규칙 타입과 값을 검증한다.
|
||||
4. 기존 권한 세트를 transaction 안에서 갱신한다.
|
||||
5. VPD 정책이 참조하는 테이블 구조에 맞춰 권한 매핑을 저장한다.
|
||||
6. audit log를 남긴다.
|
||||
7. HTMX fragment로 저장된 권한 요약을 반환한다.
|
||||
|
||||
### Token 발급
|
||||
|
||||
1. 관리자가 사용자와 만료일을 선택한다.
|
||||
2. Service가 사용자 활성 상태와 만료일을 검증한다.
|
||||
3. `vpd_live_` prefix와 충분한 난수 token body를 생성한다.
|
||||
4. server-side pepper를 사용해 hash를 만든다.
|
||||
5. prefix, hash, 만료일만 저장한다.
|
||||
6. 원문 token은 발급 완료 화면에 한 번만 표시한다.
|
||||
|
||||
### ORDS 검증
|
||||
|
||||
1. 관리자가 token과 보호 객체를 선택한다.
|
||||
2. Service가 token 상태와 보호 객체 whitelist를 확인한다.
|
||||
3. ORDS URL은 DB에 등록된 `ords_path`와 서버 설정의 base URL로만 만든다.
|
||||
4. `Authorization: Bearer <token>` 헤더로 ORDS를 호출한다.
|
||||
5. JSON 응답이면 컬럼 목록과 행 수를 계산한다.
|
||||
6. 오류 응답이면 HTTP 상태와 Oracle 오류 코드를 분류한다.
|
||||
7. audit log를 남기고 화면에 결과를 표시한다.
|
||||
|
||||
## 9. 엣지케이스 & 에러 처리
|
||||
|
||||
- Bearer Token 없음: `MISSING_TOKEN`으로 표시하고 ORDS 호출하지 않는다.
|
||||
- 만료/회수 token: `TOKEN_INACTIVE`로 표시하고 ORDS 호출하지 않는다.
|
||||
- 잘못된 token hash: `INVALID_TOKEN` 또는 ORDS 응답의 `ORA-20002`로 표시한다.
|
||||
- 보호 객체 비활성: `OBJECT_DISABLED`로 차단한다.
|
||||
- 권한 매핑 없음: 호출은 성공하지만 0 rows이면 `VPD_DENY_EMPTY_RESULT`로 설명한다.
|
||||
- 객체 DB 권한 없음: `ORA-00942`, `ORA-01031`을 `OBJECT_NOT_ACCESSIBLE`로 분류한다.
|
||||
- Redaction 적용: 민감 컬럼 값이 NULL이면 오류가 아니라 `MASKED_COLUMN` 표시로 보여준다.
|
||||
- ORDS timeout: 기본 10초 timeout, 재시도 없음. 검증 화면에서 재실행하도록 한다.
|
||||
- Audit 저장 실패: 권한 변경 작업은 rollback한다. 조회 검증 audit 실패는 오류를 반환한다.
|
||||
|
||||
## 10. 테스트 계획
|
||||
|
||||
- 단위 테스트:
|
||||
- Token 생성 결과가 충분한 길이와 prefix를 갖고 hash만 저장되는지 검증
|
||||
- 만료/회수 token 검증 실패
|
||||
- 보호 객체 whitelist 위반 차단
|
||||
- ORDS 오류 body의 `ORA-20002`, `ORA-00942`, `ORA-01031` 분류
|
||||
- 권한 저장 command의 중복/빈 rule 검증
|
||||
- 통합 테스트:
|
||||
- MyBatis Mapper XML 로딩 테스트
|
||||
- Testcontainers 사용은 Oracle 제약 때문에 초기 범위에서 제외하고 Mapper SQL은 `@MybatisTest`와 mock datasource 중심으로 검증
|
||||
- 실제 ADB 연동 테스트는 `.env`가 있는 개발 환경에서 `mvn test -Pwith-adb`로 분리
|
||||
- 화면 테스트:
|
||||
- Controller slice test로 목록/저장/검증 fragment 렌더링 확인
|
||||
- 수동 smoke test로 토큰 발급, 권한 저장, ORDS 검증 실행 확인
|
||||
|
||||
## 11. 리스크 & 대안 검토
|
||||
|
||||
- 프론트 대안:
|
||||
- React/Vue SPA는 화면 표현력은 높지만 빌드 체인과 API 경계가 늘어난다.
|
||||
- 이 백오피스는 서버 렌더링 폼, 테이블, 결과 fragment가 중심이므로 Thymeleaf + HTMX + Alpine.js + Bootstrap 5가 가장 단순하다.
|
||||
- SQL 안전성:
|
||||
- 객체명 whitelist 없이 `SELECT * FROM ${table}`을 만들면 SQL injection과 권한 우회 위험이 있다.
|
||||
- 따라서 ORDS 호출 대상은 `CB_PROTECTED_OBJECT`의 `ords_path`만 사용한다.
|
||||
- Token 저장:
|
||||
- 평문 저장은 운영 위험이 크다.
|
||||
- hash 저장과 one-time display를 기본으로 한다.
|
||||
- DDS:
|
||||
- DDS UI까지 포함하면 범위가 커진다.
|
||||
- 현재 요구는 VPD 형태와 ORDS Bearer 검증이 핵심이므로 DDS는 상태 조회/문서 연결만 후속 범위로 둔다.
|
||||
|
||||
## 12. 미해결 질문 (Open Questions)
|
||||
|
||||
- ORDS 검증 API는 기존 `sql/adb/22_agent_ords_security_ords_handler_setup.sql`의 Handler를 그대로 사용할지, 백오피스 전용 Handler를 추가할지 결정이 필요하다.
|
||||
- 백오피스 관리자 로그인은 초기에는 local user로 둘지, 사내 인증과 연결할지 후속 결정이 필요하다.
|
||||
- 컬럼 정책을 Redaction DDL까지 자동 생성할지, 관리 테이블 저장 후 DBA 적용으로 둘지 결정이 필요하다.
|
||||
- 실제 구현 issue를 별도 Redmine 하위 이슈로 나눌지, #424를 Developer 단계로 계속 이동할지 결정이 필요하다.
|
||||
86
docs/design/424-spring-boot-vpd-backoffice/fn-issueToken.md
Normal file
86
docs/design/424-spring-boot-vpd-backoffice/fn-issueToken.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# 함수 설계서: `issueToken` (#424)
|
||||
|
||||
> **부모 설계서**: ./README.md · **상태**: Draft
|
||||
> **작성**: [AI] Architect · **구현**: `BearerTokenService.issueToken` · **테스트**: `BearerTokenServiceTest`
|
||||
|
||||
## 1. 시그니처
|
||||
|
||||
```java
|
||||
IssuedToken issueToken(TokenIssueCommand command)
|
||||
```
|
||||
|
||||
## 2. 책임 (단일 책임, 1줄)
|
||||
|
||||
활성 사용자에게 새 Bearer Token을 발급하고 DB에는 원문이 아닌 hash와 prefix만 저장한다.
|
||||
|
||||
## 3. 입력
|
||||
|
||||
| 파라미터 | 타입 | 제약/검증 | 설명 |
|
||||
|----------|------|-----------|------|
|
||||
| `command.userId` | `long` | 활성 사용자여야 함 | 토큰 소유 사용자 |
|
||||
| `command.expiresAt` | `OffsetDateTime` | 현재 시각 이후, 최대 정책 기간 이내 | 만료 시각 |
|
||||
| `command.description` | `String` | 200자 이하 | 운영자 식별 설명 |
|
||||
|
||||
## 4. 출력
|
||||
|
||||
- **반환**: `IssuedToken`
|
||||
- `keyId`: 저장된 key id
|
||||
- `prefix`: 화면 식별용 prefix
|
||||
- `plainToken`: 발급 직후 한 번만 보여줄 원문 token
|
||||
- `expiresAt`: 만료 시각
|
||||
- **부수효과**: `CB_AGENT_BEARER_KEY` insert, audit insert
|
||||
|
||||
## 5. 동작 / 알고리즘
|
||||
|
||||
1. `userId`로 사용자를 조회하고 `active_yn = 'Y'`인지 확인한다.
|
||||
2. 만료일이 현재 시각 이후인지 확인한다.
|
||||
3. 정책상 최대 유효 기간을 넘으면 실패한다.
|
||||
4. `vpd_live_` prefix와 256bit 이상 난수 body를 생성한다.
|
||||
5. 원문 token을 조합한다.
|
||||
6. server-side pepper를 읽어 HMAC-SHA256 hash를 만든다.
|
||||
7. `key_prefix`, `key_hash`, `expires_at`, `created_by`를 저장한다.
|
||||
8. audit log를 저장한다.
|
||||
9. 원문 token을 포함한 `IssuedToken`을 반환한다.
|
||||
|
||||
## 6. 에러 & 실패 모드
|
||||
|
||||
| 조건 | 처리 | 반환/예외 |
|
||||
|------|------|-----------|
|
||||
| 사용자가 없음 | 발급 중단 | `UserNotFoundException` |
|
||||
| 비활성 사용자 | 발급 중단 | `InactiveUserException` |
|
||||
| 만료일이 과거 | 발급 중단 | `InvalidTokenExpiryException` |
|
||||
| pepper 설정 없음 | 발급 중단 | `TokenConfigurationException` |
|
||||
| DB 저장 실패 | transaction rollback | `DataAccessException` |
|
||||
|
||||
## 7. 엣지케이스
|
||||
|
||||
- 같은 사용자에게 여러 token 발급은 허용한다.
|
||||
- prefix는 식별용이므로 중복 가능성을 낮추되, 인증 판단에는 hash만 사용한다.
|
||||
- 원문 token은 log, audit, exception message에 포함하지 않는다.
|
||||
|
||||
## 8. 복잡도 / 성능
|
||||
|
||||
- 시간 복잡도는 O(1)이다.
|
||||
- 호출 빈도는 낮고 운영자 작업 단위다.
|
||||
- 난수 생성은 `SecureRandom` 또는 JDK 보안 API를 사용한다.
|
||||
|
||||
## 9. 의존성
|
||||
|
||||
- `UserMapper`
|
||||
- `BearerTokenMapper`
|
||||
- `AuditService`
|
||||
- `TokenHasher`
|
||||
- `Clock`
|
||||
|
||||
## 10. 테스트 케이스
|
||||
|
||||
- [ ] 정상: 활성 사용자와 미래 만료일 입력 시 원문 token과 저장 id 반환
|
||||
- [ ] 정상: 저장된 값에 원문 token이 포함되지 않음
|
||||
- [ ] 실패: 비활성 사용자 입력 시 예외
|
||||
- [ ] 실패: 과거 만료일 입력 시 예외
|
||||
- [ ] 실패: pepper 설정 없음
|
||||
|
||||
## 11. 추적성
|
||||
|
||||
- 인수조건: #424의 "Bearer Token은 발급 시 원문을 한 번만 보여주고 DB에는 hash, prefix, 만료/회수 상태만 저장한다."
|
||||
- 관련 ADR: 없음
|
||||
97
docs/design/424-spring-boot-vpd-backoffice/fn-runProbe.md
Normal file
97
docs/design/424-spring-boot-vpd-backoffice/fn-runProbe.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 함수 설계서: `runProbe` (#424)
|
||||
|
||||
> **부모 설계서**: ./README.md · **상태**: Draft
|
||||
> **작성**: [AI] Architect · **구현**: `OrdsProbeService.runProbe` · **테스트**: `OrdsProbeServiceTest`
|
||||
|
||||
## 1. 시그니처
|
||||
|
||||
```java
|
||||
ProbeResult runProbe(ProbeCommand command)
|
||||
```
|
||||
|
||||
## 2. 책임 (단일 책임, 1줄)
|
||||
|
||||
선택된 Bearer Token과 보호 객체 whitelist를 사용해 ORDS 검증 호출을 실행하고 결과 또는 오류를 분류한다.
|
||||
|
||||
## 3. 입력
|
||||
|
||||
| 파라미터 | 타입 | 제약/검증 | 설명 |
|
||||
|----------|------|-----------|------|
|
||||
| `command.keyId` | `long` | 활성 token이어야 함 | 사용할 Bearer Token |
|
||||
| `command.objectId` | `long` | 활성 보호 객체 | ORDS 호출 대상 |
|
||||
| `command.limit` | `int` | 1 이상 500 이하 | 조회 행 제한 |
|
||||
|
||||
## 4. 출력
|
||||
|
||||
- **반환**: `ProbeResult`
|
||||
- `status`: 성공/차단/오류 분류
|
||||
- `columns`: 반환 컬럼
|
||||
- `rows`: 결과 행
|
||||
- `rowCount`: 행 수
|
||||
- `maskedColumns`: NULL 또는 마스킹으로 판단된 컬럼
|
||||
- `errorCode`, `errorMessage`: 오류 시 분류 정보
|
||||
- **부수효과**: ORDS HTTP 호출, audit insert
|
||||
|
||||
## 5. 동작 / 알고리즘
|
||||
|
||||
1. `keyId`로 token 상태를 조회한다.
|
||||
2. token이 만료 또는 회수 상태면 ORDS 호출 없이 실패 결과를 반환한다.
|
||||
3. `objectId`로 보호 객체를 조회하고 활성 상태를 확인한다.
|
||||
4. ORDS URL은 설정된 base URL과 보호 객체의 `ords_path`로 만든다.
|
||||
5. 요청 timeout과 행 제한 parameter를 적용한다.
|
||||
6. `Authorization: Bearer <token>` 헤더로 ORDS를 호출한다.
|
||||
7. 2xx 응답이면 JSON을 파싱해 컬럼과 행 수를 계산한다.
|
||||
8. 민감 컬럼이 모두 NULL인 경우 Redaction 적용 표시를 만든다.
|
||||
9. 비 2xx 또는 Oracle 오류 body이면 `classifyProbeError`로 분류한다.
|
||||
10. audit log를 저장한다.
|
||||
11. 화면 표시용 `ProbeResult`를 반환한다.
|
||||
|
||||
## 6. 에러 & 실패 모드
|
||||
|
||||
| 조건 | 처리 | 반환/예외 |
|
||||
|------|------|-----------|
|
||||
| token 없음 | ORDS 호출 안 함 | `TOKEN_NOT_FOUND` |
|
||||
| token 만료/회수 | ORDS 호출 안 함 | `TOKEN_INACTIVE` |
|
||||
| 보호 객체 없음/비활성 | ORDS 호출 안 함 | `OBJECT_DISABLED` |
|
||||
| ORDS timeout | audit 후 오류 반환 | `ORDS_TIMEOUT` |
|
||||
| `ORA-20002` | 잘못된 key로 분류 | `INVALID_TOKEN` |
|
||||
| `ORA-00942` / `ORA-01031` | 객체 권한 없음으로 분류 | `OBJECT_NOT_ACCESSIBLE` |
|
||||
| 2xx + 빈 rows | VPD deny 가능성 표시 | `VPD_DENY_EMPTY_RESULT` |
|
||||
| 알 수 없는 오류 | 원문 일부만 표시 | `UNKNOWN_ERROR` |
|
||||
|
||||
## 7. 엣지케이스
|
||||
|
||||
- 응답이 JSON이 아니면 성공으로 보지 않고 `INVALID_ORDS_RESPONSE`로 분류한다.
|
||||
- 결과가 500행을 넘지 않도록 limit 기본값과 최대값을 둔다.
|
||||
- audit에는 token 원문을 저장하지 않고 `key_id`, `key_prefix`만 저장한다.
|
||||
- ORDS 응답 body 전체를 audit에 저장하지 않고 오류 코드와 짧은 메시지만 저장한다.
|
||||
|
||||
## 8. 복잡도 / 성능
|
||||
|
||||
- 네트워크 I/O가 지배적이다.
|
||||
- 기본 timeout은 10초다.
|
||||
- 화면 검증 호출이므로 자동 재시도는 하지 않는다.
|
||||
|
||||
## 9. 의존성
|
||||
|
||||
- `BearerTokenMapper`
|
||||
- `ProtectedObjectMapper`
|
||||
- `RestClient` 또는 `WebClient`
|
||||
- `ProbeErrorClassifier`
|
||||
- `AuditService`
|
||||
- `Clock`
|
||||
|
||||
## 10. 테스트 케이스
|
||||
|
||||
- [ ] 정상: 2xx JSON 응답에서 컬럼과 행 수 추출
|
||||
- [ ] 정상: 빈 rows를 VPD deny 가능성으로 표시
|
||||
- [ ] 정상: 민감 컬럼 NULL을 masked column으로 표시
|
||||
- [ ] 실패: 만료 token이면 ORDS 호출하지 않음
|
||||
- [ ] 실패: 비활성 객체이면 ORDS 호출하지 않음
|
||||
- [ ] 실패: `ORA-20002` 분류
|
||||
- [ ] 실패: timeout 분류
|
||||
|
||||
## 11. 추적성
|
||||
|
||||
- 인수조건: #424의 "ORDS 호출 검증 화면에서 Bearer Token과 보호 객체를 선택하면 ORDS 호출 결과, 반환 컬럼, 행 수, 오류 유형을 확인할 수 있다."
|
||||
- 관련 ADR: 없음
|
||||
@@ -0,0 +1,88 @@
|
||||
# 함수 설계서: `savePermissionSet` (#424)
|
||||
|
||||
> **부모 설계서**: ./README.md · **상태**: Draft
|
||||
> **작성**: [AI] Architect · **구현**: `PermissionService.savePermissionSet` · **테스트**: `PermissionServiceTest`
|
||||
|
||||
## 1. 시그니처
|
||||
|
||||
```java
|
||||
PermissionSet savePermissionSet(PermissionSetCommand command)
|
||||
```
|
||||
|
||||
## 2. 책임 (단일 책임, 1줄)
|
||||
|
||||
역할과 보호 객체에 대한 행 규칙 및 컬럼 표시 정책을 검증한 뒤 하나의 transaction으로 저장한다.
|
||||
|
||||
## 3. 입력
|
||||
|
||||
| 파라미터 | 타입 | 제약/검증 | 설명 |
|
||||
|----------|------|-----------|------|
|
||||
| `command.roleId` | `long` | 존재하는 역할 | 권한을 받을 역할 |
|
||||
| `command.objectId` | `long` | 활성 보호 객체 | 조회 대상 객체 |
|
||||
| `command.action` | `String` | 초기 구현은 `SELECT`만 허용 | 작업 유형 |
|
||||
| `command.rules` | `List<RuleCommand>` | 1개 이상, 타입별 값 검증 | 행 조건 |
|
||||
| `command.visibleColumns` | `List<String>` | 보호 객체에 등록된 컬럼만 허용 | 표시 허용 컬럼 |
|
||||
|
||||
## 4. 출력
|
||||
|
||||
- **반환**: 저장된 `PermissionSet`
|
||||
- **부수효과**: permission/rule/column mapping 갱신, audit insert
|
||||
|
||||
## 5. 동작 / 알고리즘
|
||||
|
||||
1. 역할이 존재하는지 확인한다.
|
||||
2. 보호 객체가 whitelist에 있고 활성 상태인지 확인한다.
|
||||
3. action이 허용 값인지 확인한다.
|
||||
4. rule 목록이 비어 있으면 실패한다.
|
||||
5. rule type별 rule value 형식을 검증한다.
|
||||
6. 중복 rule을 제거하거나 실패 처리한다. 초기 구현은 실패 처리한다.
|
||||
7. 표시 컬럼 목록이 보호 객체 컬럼 목록의 부분집합인지 확인한다.
|
||||
8. transaction 안에서 기존 권한 세트를 갱신한다.
|
||||
9. audit log를 저장한다.
|
||||
10. 저장된 권한 세트를 다시 조회해 반환한다.
|
||||
|
||||
## 6. 에러 & 실패 모드
|
||||
|
||||
| 조건 | 처리 | 반환/예외 |
|
||||
|------|------|-----------|
|
||||
| 역할 없음 | 저장 중단 | `RoleNotFoundException` |
|
||||
| 보호 객체 없음/비활성 | 저장 중단 | `ProtectedObjectNotFoundException` |
|
||||
| 허용되지 않은 action | 저장 중단 | `InvalidPermissionActionException` |
|
||||
| rule 없음 | 저장 중단 | `InvalidPermissionRuleException` |
|
||||
| rule value 형식 오류 | 저장 중단 | `InvalidPermissionRuleException` |
|
||||
| 컬럼 whitelist 위반 | 저장 중단 | `InvalidColumnPolicyException` |
|
||||
| audit 저장 실패 | rollback | `DataAccessException` |
|
||||
|
||||
## 7. 엣지케이스
|
||||
|
||||
- `ALL` rule과 다른 제한 rule이 같이 들어오면 충돌로 보고 실패한다.
|
||||
- `SELF` rule은 대상 객체에 사용자 식별 컬럼이 등록되어 있어야 허용한다.
|
||||
- `MY_DEPT` rule은 대상 객체에 부서 컬럼이 등록되어 있어야 허용한다.
|
||||
- 컬럼 목록이 빈 값이면 민감 컬럼 표시 없음으로 처리한다.
|
||||
|
||||
## 8. 복잡도 / 성능
|
||||
|
||||
- rule/column 검증은 입력 목록 크기에 대해 O(n)이다.
|
||||
- 운영자 저장 작업 단위이므로 대량 처리 성능은 주요 병목이 아니다.
|
||||
|
||||
## 9. 의존성
|
||||
|
||||
- `RoleMapper`
|
||||
- `ProtectedObjectMapper`
|
||||
- `PermissionMapper`
|
||||
- `AuditService`
|
||||
- `ObjectNamePolicy`
|
||||
|
||||
## 10. 테스트 케이스
|
||||
|
||||
- [ ] 정상: SELECT 권한과 REGION rule 저장
|
||||
- [ ] 정상: 민감 컬럼 제외 정책 저장
|
||||
- [ ] 실패: `ALL`과 `REGION` rule 동시 입력
|
||||
- [ ] 실패: 비활성 보호 객체 입력
|
||||
- [ ] 실패: 등록되지 않은 컬럼 입력
|
||||
- [ ] 실패: audit 저장 실패 시 permission 저장 rollback
|
||||
|
||||
## 11. 추적성
|
||||
|
||||
- 인수조건: #424의 "내부 사용자, 역할, 사용자-역할, 객체 권한, 행 규칙, 컬럼 표시 권한을 백오피스에서 조회하고 저장할 수 있다."
|
||||
- 관련 ADR: 없음
|
||||
Reference in New Issue
Block a user