설계서: Spring Boot VPD/ORDS 권한 백오피스 (#424)
상태: Draft 작성: [AI] Architect · 최종수정: 2026-06-23 추적성 — Redmine: #424 · 관련 ADR: 없음 · 구현 파일:
vpd-backoffice/pom.xml,vpd-backoffice/src/main/**,vpd-backoffice/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 적용 결과를 확인하는 화면 - ORDS 검증 결과를 내부 도구 실행 결과로 감싸 OpenAI 호환 LLM에 전달하는 MCP-style reasoning 화면
- 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 원문, AI API Key는 git에 저장하지 않는다.- 객체명은 반드시 DB에 등록된 보호 객체 whitelist로 제한한다.
- 백오피스 DB 계정은 권한 관리 테이블과 검증용 ORDS 호출에 필요한 최소 권한만 갖는다.
- VPD는 행(row) 통제, Redaction은 컬럼 값 마스킹/NULL 처리로 설명하고 구현한다.
- 가정:
- 기존 SQL의
CB_*ORDS/VPD Bearer Key 예제를 백오피스 데이터 모델의 기준으로 삼는다. - 첫 구현은 단일 관리자 로그인 또는 개발용 in-memory user로 시작하고, 운영 SSO는 후속 범위로 둔다.
- ORDS 호출은 백오피스 서버에서 수행하며 브라우저에는 Bearer Token 원문을 보존하지 않는다.
- 기존 SQL의
5. 아키텍처 개요
모듈/파일 구조
vpd-backoffice/pom.xml
vpd-backoffice/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
vpd-backoffice/src/main/resources/
application.yml
mapper/*.xml
templates/**/*.html
static/css/app.css
static/js/app.js
vpd-backoffice/src/test/java/com/cloudhandson/vpdbackoffice/
데이터 흐름
관리자 브라우저
-> Spring MVC Controller
-> Service
-> MyBatis Mapper
-> ADB 권한/토큰/보호 객체 테이블
검증 호출
-> ProbeController
-> OrdsProbeService
-> BearerTokenService에서 token 상태 확인
-> ProtectedObject whitelist 확인
-> ORDS HTTP 호출 Authorization: Bearer <token>
-> 응답/오류 분류
-> Audit 저장
-> Thymeleaf fragment로 결과 영역 갱신
MCP-style reasoning
-> McpReasoningController
-> McpReasoningService
-> 내부 tool registry에서 보호 객체별 ORDS 조회 도구 선택
-> OrdsProbeService로 Bearer Token 기반 조회 실행
-> 조회 rows/request/response 증거를 OpenAI 호환 Chat Completions API에 전달
-> AI 응답과 도구 실행 증거를 화면에 함께 표시
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에는 저장하지 않는다.
- 기존 ORDS 패키지가
STANDARD_HASH(p_bearer_key, 'SHA256')로 Bearer Key를 검증하므로key_hash는 SHA-256으로 저장한다. HMAC 또는 pepper 적용은 DB 패키지 검증 방식까지 함께 바꾸는 후속 작업으로 둔다. 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.mdfn-savePermissionSet.mdfn-runProbe.md
8. 흐름 / 알고리즘
권한 저장
- Controller가 역할, 보호 객체, 행 규칙, 컬럼 표시 정책 입력을 받는다.
- Service가 role/object 존재와 활성 상태를 확인한다.
- 행 규칙 타입과 값을 검증한다.
- 기존 권한 세트를 transaction 안에서 갱신한다.
- VPD 정책이 참조하는 테이블 구조에 맞춰 권한 매핑을 저장한다.
- audit log를 남긴다.
- HTMX fragment로 저장된 권한 요약을 반환한다.
Token 발급
- 관리자가 사용자와 만료일을 선택한다.
- Service가 사용자 활성 상태와 만료일을 검증한다.
vpd_live_prefix와 충분한 난수 token body를 생성한다.- 기존 ORDS 패키지와 호환되는 SHA-256 hash를 만든다.
- prefix, hash, 만료일만 저장한다.
- 원문 token은 발급 완료 화면에 한 번만 표시한다.
ORDS 검증
- 관리자가 token과 보호 객체를 선택한다.
- Service가 token 상태와 보호 객체 whitelist를 확인한다.
- ORDS URL은 DB에 등록된
ords_path와 서버 설정의 base URL로만 만든다. Authorization: Bearer <token>헤더로 ORDS를 호출한다.- JSON 응답이면 컬럼 목록과 행 수를 계산한다.
- 오류 응답이면 HTTP 상태와 Oracle 오류 코드를 분류한다.
- 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만 사용한다.
- 객체명 whitelist 없이
- Token 저장:
- 평문 저장은 운영 위험이 크다.
- hash 저장과 one-time display를 기본으로 한다.
- DDS:
- DDS UI까지 포함하면 범위가 커진다.
- 현재 요구는 VPD 형태와 ORDS Bearer 검증이 핵심이므로 DDS는 상태 조회/문서 연결만 후속 범위로 둔다.
12. 미해결 질문 (Open Questions)
- ORDS 검증 API는 기존
database/adb/22_agent_ords_security_ords_handler_setup.sql의 Handler를 그대로 사용할지, 백오피스 전용 Handler를 추가할지 결정이 필요하다. - 백오피스 관리자 로그인은 초기에는 local user로 둘지, 사내 인증과 연결할지 후속 결정이 필요하다.
- 컬럼 정책을 Redaction DDL까지 자동 생성할지, 관리 테이블 저장 후 DBA 적용으로 둘지 결정이 필요하다.
- 실제 구현 issue를 별도 Redmine 하위 이슈로 나눌지, #424를 Developer 단계로 계속 이동할지 결정이 필요하다.
13. MCP-style Reasoning 추가 설계
초기 구현은 외부 MCP client 라이브러리를 사용하지 않는다. 백오피스 내부에서 보호 객체를 도구 목록으로 만들고, 도구 실행은 기존 OrdsProbeService를 재사용한다. 이렇게 하면 실제 ORDS/VPD 결과를 그대로 증거로 삼으면서도, 추후 표준 MCP server endpoint로 분리하기 쉽다.
설정
| 환경변수 | 설명 |
|---|---|
BACKOFFICE_AI_ENABLED |
AI 호출 활성화 여부 |
BACKOFFICE_AI_BASE_URL |
OpenAI 호환 API base URL |
BACKOFFICE_AI_MODEL |
호출할 모델명 |
BACKOFFICE_AI_API_KEY |
실제 호출에 사용할 API Key |
BACKOFFICE_AI_TIMEOUT_SECONDS |
AI 호출 timeout |
vpdtest1, vpdtest2 같은 키 별칭은 .env에 보관하고, 운영자가 선택한 값을 BACKOFFICE_AI_API_KEY에 연결한다.