Skip to content

위임 토큰(on-behalf-of) 계약 — PortalConsole 발급 ​

작성 시각: 2026-09-13 · 상태: 제안(소비 서비스 합의 필요) 관련 문서: docs/portal-central-auth-contract.md(중앙 로그인·PKCE), server/serviceTokenRoute.ts(현행 서비스 토큰), server/userIdentity.ts(사용자 JWT) 요청 배경: RagService는 JWT 클레임만 신뢰하고 tenant·조직·등급을 헤더·본문으로 받지 않는다(RagService/docs/ARCHITECTURE.md). 현재 PortalConsole는 tenant_id·organization_id·clearance_level 클레임과 rag-center audience·rag:* scope를 발급하지 않아 서버 간 연동이 성립하지 않는다(ProcessCenter/docs/RAGCENTER_INTEGRATION_PLAN.md §2-1-1).

1. 목적 ​

소비 서비스(RagService, ExploreGateway, 각 포털 등)가 특정 사용자의 권한으로 서버 간 호출을 수행할 수 있도록, PortalConsole가 사용자 컨텍스트를 담은 단기 토큰을 발급한다. 위임 토큰은 권한을 넓히지 않으며(audience·scope는 축소만), 위임 주체를 항상 기록한다.

2. 발급 경로 ​

경로방식사용 시점판단
A. 사용자 토큰 교환(권장)사용자 JWT(Authorization: Bearer) + audience·scope를 DS에 제출 → 좁힌 위임 토큰 발급포털이 이미 사용자 세션을 보유권장 — 사용자 본인 동의 맥락이 유지되고 scope 상승이 불가
B. 서버 위임 발급소비 서비스 client_id·client_secret + subject(directory user id)로 발급포털이 이미 사용자를 인증했고 배치·동기화를 수행허용 — act로 위임 주체를 남기고 감사 대상
C. 서비스 전용 토큰기존 /api/internal/service-token(사용자 없음)문서 수집·동기화처럼 사용자 컨텍스트가 불필요한 작업유지(검색·답변에는 부적합)

엔드포인트(제안)

text
POST /api/directory/v1/auth/token/delegation      (경로 A: 사용자 토큰 교환)
Authorization: Bearer <directory user JWT>
{ "audience": "rag-center", "scope": "rag:read" }
→ { "access_token": "...", "token_type": "Bearer", "expires_in": 600 }

POST /api/internal/delegation-token                (경로 B: 서버 위임 발급)
{ "client_id": "process-center", "client_secret": "...", "subject": "<directory user id>",
  "audience": "rag-center", "scope": "rag:read" }
→ { "access_token": "...", "token_type": "Bearer", "expires_in": 600 }

3. 클레임 계약 ​

클레임값·출처필수비고
issdirectory-service✔기존과 동일
aud요청한 소비 서비스 audience(rag-center)✔요청은 허용 목록 내에서만
subdirectory user id (UUID)✔기존 사용자 JWT와 동일
act위임 주체 서비스(process-center)✔감사·추적용(경로 A에서는 azp와 동일 값)
azp원 포털 ID✔기존 규칙 유지
scope요청 scope의 부분집합(예: rag:read)✔상승 금지(사용자 보유 scope ∩ 요청 scope)
token_usedelegation✔기존 service·사용자 토큰과 구분
tenant_id신규 — 배포 테넌트 식별자(미도입 시 설정값 default)✔DS에 테넌트 개념 없음 → 설정/기본값으로 시작
organization_id신규 — 대표 조직(departmentId)✔directoryDepartments 재사용
organizationIds신규 — 조직 트리(상위 포함) 목록선택RagService 수집 계약의 organizationIds 대응
clearance_level신규 — 사용자 등급(기본 INTERNAL)✔directory_users.clearance_level 속성 추가 필요
department_code부서 코드선택UUID·코드 매핑표 필요
exp5~10분✔기존 서비스 토큰(10분)과 정합

등급 값: PUBLIC·INTERNAL·CONFIDENTIAL·SECRET(ProcessCenter securityLevel과 동일 체계 — 매핑표는 소비 서비스 문서에서 관리)

3-1. 식별자 형식 — uuidv5 투영 (2026-09-25) ✔ ​

RagService는 tenant_id·organization_id를 **PostgreSQL uuid**로 다룬다(RLS principal, source_document_id도 uuid). 따라서 DS 원천 키(default, 부서 UUID)를 발급 시점에 결정적으로 uuid로 투영한다. 투영식은 양 저장소가 같은 상수를 가지며 (PortalConsole/server/ragIdentity.ts = ProcessCenter/server/rag/ragIdentity.ts, RFC 4122 v5 = Python uuid.uuid5), 양쪽 ragIdentity.test.ts가 동일 벡터로 회귀를 막는다(어긋나면 검색이 조용히 0건이 된다).

클레임원천투영벡터(예)
tenant_idPORTAL_CONSOLE_TENANT_ID(기본 default)uuid5(tenant, 값)default → 3ce42132-d4f8-5f3e-93f8-9c8f1c565033
organization_id사용자 departmentIduuid5(organization, 부서 UUID)dept-uuid-9 → 646d68df-ed6e-5c1f-b187-71918f5e0deb
organization_id(부서 없음)unassigneduuid5(organization, "unassigned")→ 139dc4f9-6828-5f9c-b972-910f58f44b25
organization_ids소속 조직 목록같은 투영의 나열상위 조직 포함 시 순서 유지
department_code(선택)부서 코드투영 없음(사람이 읽는 값)—
  • 네임스페이스: root = uuid5(NAMESPACE_URL, "https://portal-foundation.local/rag-identity"), 하위 tenant·organization·document.
  • 부서 없는 사용자도 반드시 유효한 uuid 조직을 받는다 — 클레임 누락(RagService 401)이나 임의 uuid 생성 대신 unassigned 버킷을 공유한다.
  • 부서 코드↔UUID 매핑(§8-5)은 이 투영과 별개다: RLS 주체는 부서 id 기반 uuid, 표시·조인용은 department_code다.

4. 소비 서비스 검증 요구사항 ​

소비 서비스는 위임 토큰을 다음 순서로 검증한다.

  1. 서명·발급자 — HS256(현행) 또는 합의한 알고리즘, iss = directory-service, 서명키 공유(PORTAL_JWT_SECRET = PORTAL_CONSOLE_JWT_SIGNING_KEY 동일 값 주입)
  2. 대상 — aud가 자기 서비스 audience와 정확히 일치(불일치 시 401)
  3. 용도·만료 — token_use = delegation, exp 미경과
  4. 필수 클레임 존재 — sub·tenant_id·organization_id·clearance_level·scope
  5. 권한 강제 — 클레임을 RLS principal로 변환해 쿼리 단계에서 적용(RagService 방식). 클레임을 요청 파라미터로 덮어쓸 수 없어야 한다.
  6. 감사 — act(위임 주체)·sub·azp·요청 ID를 감사 로그에 남긴다.

5. PortalConsole 구현 변경 목록 ​

구현 상태(2026-09-13) ✔

#변경상태
1서비스 토큰 일반화✅ server/serviceTokenConsumers.ts — PORTAL_CONSOLE_TOKEN_CONSUMERS_JSON(항목: clientId·clientSecret·audience·scopes·delegationAudiences·delegationScopes), 레거시(PROCESS_CENTER_SERVICE_CLIENT_SECRET) 호환 유지
2위임 발급 엔드포인트✅ POST /api/internal/delegation-token(server/delegationTokenRoute.ts) — 소비자 인증 → subject 사용자 확인 → 클레임 발급(10분)
3클레임 생성✅ sub·act·azp·scope(허용 목록 교집합)·token_use=delegation·tenant_id·organization_id(+organization_ids)·clearance_level·login_id — tenant_id·조직은 uuidv5 투영(§3-1)
4사용자 등급 속성⏳ 부분 — clearance_level은 PORTAL_CONSOLE_DEFAULT_CLEARANCE_LEVEL(기본 INTERNAL)이다. 사용자별 등급 속성(directory_users.clearance_level)은 후속
5테넌트 설정✅ PORTAL_CONSOLE_TENANT_ID(기본 default), organization_id는 사용자 부서(directory_users.departmentId)
6문서 상호 링크✅ docs/portal-central-auth-contract.md

검증: 단위 테스트(serviceTokenConsumers.test.ts·delegationTokenRoute.test.ts·ragIdentity.test.ts — 식별자 투영 벡터 포함) + 개발 스택 e2e — 위임 토큰 발급 → ExploreGateway 탐색 성공, 잘못된 시크릿 401, 레거시 서비스 토큰(테마) 정상.

환경변수 ​

변수예설명
PORTAL_CONSOLE_TOKEN_CONSUMERS_JSON[{ "clientId": "process-center", "clientSecret": "...", "audience": "ui-center-theme", "scopes": [...], "delegationAudiences": ["rag-center","explore-gateway","directory-user"], "delegationScopes": ["rag:read","rag:sync","rag:admin","search:read","directory:user"] }]소비자 등록(없으면 레거시 동작). 삭제 전파에는 rag:admin이 필요하다 — 목록에 없으면 scope_not_allowed(400)로 색인 삭제가 조용히 실패한다. 개발 스택 값은 ProcessCenter/docker-compose.dev.yml portal-console 서비스 env 다(2026-09-25 확인 — delegationAudiences에 rag-center, delegationScopes에 rag:admin 포함). 운영은 각 배포 compose/env 에 같은 항목을 넣는다
PORTAL_CONSOLE_TENANT_IDdefault위임 토큰 tenant_id
PORTAL_CONSOLE_DEFAULT_CLEARANCE_LEVELINTERNAL위임 토큰 clearance_level 기본값

위임 소비자 목록 (2026-10-05 기준) ​

clientIddelegationAudiencesdelegationScopes쓰는 곳
process-centerrag-center, explore-gateway, directory-userrag:read, rag:sync, rag:admin, search:read, directory:userProcessCenter — 문서 색인 발행(rag:*) + 상단 검색 답변 위임(explore-gateway) + 프로필 사진/비밀번호 변경(directory:user)
agent-centerexplore-gatewaysearch:readAgentCenter — 상단 검색 답변 위임(explore.ask)
document-libexplore-gateway, directory-usersearch:read, directory:userDocument Library — 상단 검색 답변 위임(POST /api/explore/ask) + 프로필 사진/비밀번호 변경(directory:user)
guide-centerexplore-gateway, directory-usersearch:read, directory:userGuide Center — 상단 검색 답변 위임(trpc.explore.ask) + 프로필 사진/비밀번호 변경(directory:user)
  • 각 포털의 clientSecret 은 그 포털 서버 env 와 같은 값이다(PROCESS_CENTER_SERVICE_CLIENT_SECRET·AGENT_CENTER_SERVICE_CLIENT_SECRET·DOCUMENT_LIBRARY_SERVICE_CLIENT_SECRET·GUIDE_CENTER_SERVICE_CLIENT_SECRET).
  • delegationAudiences 밖의 audience 는 거절된다 — 위임은 좁히기만 한다.
  • ExploreGateway 는 이 목록에 없다: 게이트웨이는 위임 토큰을 받는 쪽(오케스트레이터)이라 발급 대상이 아니다. 대신 게이트웨이가 각 포털 어댑터를 부를 때 쓰는 서비스 토큰(PORTAL_SERVICE_TOKENS_JSON)을 따로 둔다.

원래 변경 목록(참고) ​

#변경위치내용
1서비스 토큰 일반화server/serviceTokenRoute.tsSERVICE_ID·SERVICE_AUDIENCE·SERVICE_SCOPES 하드코딩 제거 → 소비자별 등록(설정/DB: client_id·secret hash·audience·허용 scope) + rag-center(scope rag:read·rag:sync) 등록
2위임 발급 엔드포인트신규 server/delegationTokenRoute.ts경로 A(사용자 토큰 교환)·경로 B(서버 위임) 구현, audience·scope 허용 목록 검증
3클레임 생성server/userIdentity.ts(발급부) 또는 신규 모듈tenant_id·organization_id(s)·clearance_level·token_use=delegation·act 추가
4사용자 등급 속성drizzle/schema.pg.ts + 마이그레이션directory_users.clearance_level(기본 INTERNAL) 추가, 관리자 화면에서 설정
5테넌트 설정(최소 도입)설정/환경변수단일 테넌트 배포를 가정한 PORTAL_CONSOLE_TENANT_ID(기본 default). 다중 테넌트는 후속
6문서 상호 링크docs/portal-central-auth-contract.md본 계약 링크 추가(중앙 인증과 위임의 관계 명시)

6. 보안 원칙 ​

  • 위임 토큰은 권한을 넓히지 않는다 — audience·scope는 요청값의 부분집합만 허용한다.
  • 브라우저에 노출하지 않는다 — 서버 간 전용(사용자 토큰은 교환 요청에만 사용).
  • 위임 주체를 항상 기록한다(act) — "누가 누구 대신" 호출했는지 감사로 남긴다.
  • 포털 로컬 권한 우선 원칙 유지 — 중앙 인증은 신원·컨텍스트만 제공하고, 최종 업무 권한은 각 포털/RagService가 강제한다(portal-central-auth-contract.md 보안 원칙과 동일).
  • 서명키는 비대칭(RS256) 전환을 중기 과제로 검토한다(현재 HS256은 모든 소비자가 서명 가능 ✗).

7. 단계 ​

단계내용선행
D0본 계약 합의(클레임·경로·scope 목록)소비 서비스(RagService·ProcessCenter·ExploreGateway) 확인
D1서비스 토큰 일반화(소비자별 등록 + rag-center)✅ 완료(2026-09-13)
D2clearance_level 속성·관리 화면 + tenant_id 설정✅ 부분 완료(기본값·부서 기반) — 사용자별 등급 속성은 후속
D3위임 발급 엔드포인트(경로 A·B) + 검증 테스트✅ 경로 B 완료(2026-09-13) · 경로 A(사용자 토큰 교환)는 후속
D4소비 서비스 적용(RagService 클레임 검증, ProcessCenter 교환 호출)⏳ 진행 필요

8. 미해결 ​

#항목결정 필요
1HS256 유지 vs RS256 전환보안 요구 수준
2다중 테넌트 도입 시점제품 전략(현재는 단일 테넌트 가정)
3organization_id 규칙 — 대표 부서 1개 vs 소속 조직 목록RagService RLS 설계
4등급(clearance_level) 관리 주체 — 사용자 속성 vs 포털 접근권한 연동운영 정책
5부서 UUID ↔ 부서 코드 매핑소비 서비스 요구
6위임 발급 시 사용자 인증 보장 — 경로 A(교환)만 허용할지, 경로 B도 허용할지보안 정책
7토큰 회수·재발급 정책(사용자 비활성화·등급 변경 시)운영 정책