Skip to content

1단계 FileStore API 계약 ​

1. 범위 ​

이 문서는 PortalConsole에 의존하지 않고 포탈 서버가 FileStore를 직접 호출하는 1단계 연동 계약을 정의한다. 포탈은 FileStore의 저장 Backend와 자격증명을 알지 않는다. 포탈은 서버 간 인증과 포탈 식별자, FileStore 관리자 UI에서 등록한 정책 ID만 사용한다.

모든 API는 다음 기본 경로를 사용한다.

text
/api/file-store/v1

2. 인증 ​

포탈 서버가 호출하는 API에는 다음 헤더를 포함한다.

http
Authorization: Bearer <FILE_STORE_SERVICE_TOKEN>
Content-Type: application/json

서비스 토큰은 브라우저에 전달하지 않는다. 포탈의 클라이언트가 직접 FileStore API를 호출하지 않도록 하고, 포탈 서버가 요청을 중계한다.

포탈 서버는 권한 판정 결과와 추적 ID를 요청 body의 metadata로 전달할 수 있다. authorizationOperation은 API 세부 경로명이 아니라 권한 동작 종류를 나타내며, 허용 값은 read, write, delete이다. 업로드 세션과 업로드 완료는 모두 write로 표시한다.

json
{
  "requestId": "request-uuid",
  "authorizationIssuer": "process-center",
  "authorizationAllowed": true,
  "authorizationOperation": "write"
}

3. 정책 조회 ​

정책 조회는 포탈 서버가 자신의 consumerId와 mappingId를 확인할 때 사용한다.

http
GET /api/file-store/v1/consumer-policies

응답에는 저장 정책 메타데이터가 포함될 수 있지만, Access Key, Secret Key, 관리자 세션, 서버 파일 경로는 포함하지 않는다.

json
{
  "data": [
    {
      "id": "policy-uuid",
      "consumerId": "document-library",
      "backendId": "r2-primary",
      "pathPrefix": "attachments",
      "maxBytes": 104857600,
      "allowedContentTypes": ["application/pdf", "image/png"],
      "encryptionRequired": true,
      "readEnabled": true,
      "writeEnabled": true,
      "enabled": true
    }
  ]
}

4. 업로드 세션 ​

포탈 서버는 파일 자체를 먼저 보내지 않고 업로드 세션을 요청한다.

http
POST /api/file-store/v1/assets/upload-session
json
{
  "consumerId": "document-library",
  "mappingId": "policy-uuid",
  "ownerType": "document",
  "ownerId": "document-123",
  "fileName": "manual.pdf",
  "contentType": "application/pdf",
  "classification": "INTERNAL",
  "organizationIds": ["00000000-0000-0000-0000-000000000001"],
  "sourceVersion": "v1",
  "sourceMenuId": "board",
  "sourceEntityType": "post",
  "sourceEntityId": "post-123",
  "context": { "team": "hr", "revision": 3 },
  "actorSubject": "portal-user-or-service-subject",
  "metadata": {
    "requestId": "request-uuid",
    "authorizationIssuer": "process-center",
    "authorizationAllowed": true,
    "authorizationOperation": "write"
  }
}

sourceMenuId·sourceEntityType·sourceEntityId·context 는 선택 필드다. 소비 포탈이 파일의 출처(사내 메뉴·게시물 등)를 함께 넘기면 FileStore가 파일에 보존해 이후 파일 관리·분석에 쓴다. context 는 문자열/숫자/불리언 값의 평면 map 이다.

FileStore는 다음 항목을 검증한다.

  • consumerId와 mappingId의 일치 여부
  • 정책과 Backend의 활성 상태
  • write 권한
  • 파일명과 MIME type 형식
  • 포탈 정책의 저장 경로

응답에는 파일 ID와 제한된 업로드 URL이 포함된다.

json
{
  "data": {
    "asset": {
      "id": "asset-uuid",
      "consumerId": "document-library",
      "mappingId": "policy-uuid",
      "status": "PENDING_SCAN",
      "storageKey": "attachments/document-library/asset-uuid/manual.pdf"
    },
    "uploadUrl": "https://storage-provider/upload-url"
  }
}

uploadUrl은 브라우저에 전달할 수 있지만, 서비스 토큰은 전달하지 않는다. URL의 만료 시간과 사용 범위는 Storage Backend 구현에 따른다.

4.1 업로드 보안 검증 ​

FileStore는 포탈의 사전 검증과 별개로 다음 규칙을 최종 적용한다.

  • 파일명에는 제어문자, \, /, . 또는 ..만으로 된 경로 요소를 사용할 수 없다.
  • contentType에는 MIME type만 허용하며 ; charset=... 같은 parameter를 포함할 수 없다.
  • HTML, XHTML, JavaScript 및 SVG(text/html, application/xhtml+xml, application/javascript, text/javascript, image/svg+xml)는 업로드할 수 없다. SVG를 허용해야 하는 포탈은 별도 sanitization·변환 파이프라인을 먼저 제공해야 한다.
  • 직접 base64 업로드를 사용하는 호환 API는 data:<mime>;base64,...의 MIME과 contentType이 일치해야 하며, malformed base64와 빈 payload를 거부한다.
  • 파일 크기와 허용 MIME은 consumer policy의 maxBytes 및 allowedContentTypes로 다시 검증한다.

이 검증은 파일 확장자만 신뢰하지 않는다. 업로드된 객체의 실제 파일 형식 검사나 malware scanning이 필요한 경우 PENDING_SCAN 상태에서 별도 scanner가 검사한 뒤 ACTIVE로 전환해야 한다.

5. 업로드 완료 ​

파일 업로드가 끝나면 포탈 서버는 FileStore에 완료를 알린다.

http
POST /api/file-store/v1/assets/:assetId/complete
json
{
  "bytes": 48321,
  "checksum": "sha256-hex-64-characters",
  "actorSubject": "portal-user-or-service-subject"
}

FileStore는 저장 객체의 실제 크기를 확인한 뒤 파일을 ACTIVE로 전환한다. 크기가 다르거나 객체가 없으면 완료를 거부한다.

성공 응답은 업로드 세션처럼 data.asset으로 중첩하지 않고, 파일 자체를 data에 반환한다.

json
{
  "data": {
    "id": "asset-uuid",
    "status": "ACTIVE",
    "bytes": 48321,
    "checksum": "sha256-hex-64-characters"
  }
}

6. 파일 조회 ​

http
GET /api/file-store/v1/assets/:assetId

삭제된 파일은 일반 조회에서 404로 처리한다.

7. 다운로드 URL ​

http
POST /api/file-store/v1/assets/:assetId/read-url
json
{
  "actorSubject": "portal-user-or-service-subject"
}

FileStore는 파일이 ACTIVE이고 정책의 readEnabled가 true인 경우에만 locator를 발급한다. 응답 locator는 만료 시간을 가진다. 포탈은 locator를 일반 저장소 자격증명과 함께 저장하지 않는다.

파일이 없으면 404 asset_not_found를 반환한다. 일반적인 요청 형식 오류나 정책 처리 오류와 구분할 수 있도록 포탈은 이 응답을 “읽을 수 없는 파일”으로 처리해야 한다.

8. 삭제 (출처 단위) ​

삭제는 소비 포탈의 출처(게시물/노드) 수명주기가 결정한다. FileStore 는 삭제를 결정하지 않고, 포탈이 게시물/노드를 지울 때 그 출처에 묶인 파일을 출처 단위로 하드 삭제한다.

http
POST /api/file-store/v1/assets/delete-by-source
Authorization: Bearer <FILE_STORE_SERVICE_TOKEN>
X-FileStore-Consumer: document-library
json
{
  "consumerId": "document-library",
  "sourceEntityType": "document",
  "sourceEntityId": "document-123",
  "actorSubject": "directory-admin"
}

FileStore 는 레코드·저장 객체를 즉시 삭제하고 파일별 DELETED 감사 이벤트를 남긴다. 보존·법적보존 개념은 두지 않으며, 매칭이 없으면 deleted: 0(멱등)이다.

9. 상태와 오류 ​

상태의미
PENDING_SCAN업로드는 완료되었지만 아직 활성화되지 않은 상태
ACTIVE일반 읽기와 정책상 후속 작업이 가능한 상태
QUARANTINED검사 또는 보안 정책에 의해 격리된 상태
오류처리 원칙
401 service_auth_required서버 간 토큰을 확인하고 재시도한다. 브라우저에는 노출하지 않는다.
404 asset_not_found파일이 없다. 새 read URL을 재시도하지 않는다.
정책 불일치consumerId와 mappingId를 관리자 UI에서 확인한다.
비활성 정책 또는 Backend관리자 UI에서 활성 상태와 Backend 연결을 확인한다.
MIME·크기 제한 초과포탈의 사전 검증과 FileStore의 최종 검증을 모두 통과해야 한다.
업로드 객체 검증 실패업로드 URL과 완료 요청의 파일 ID를 확인한다.

10. 1단계 완료 기준 ​

다음 흐름이 실제 Backend 구현과 테스트에서 확인되면 1단계 API 계약이 완료된 것으로 본다.

text
정책 조회
  → 업로드 세션
  → 저장소 업로드
  → 업로드 완료
  → ACTIVE 전환
  → 다운로드 URL 발급
  → 출처 삭제(delete-by-source)
  → 감사 로그 확인

11. LOCAL_VOLUME 서명 업로드·다운로드 ​

LOCAL_VOLUME backend는 공개 객체 저장소가 아니므로, 4. 업로드 세션의 uploadUrl과 7. 다운로드 URL의 locator가 FileStore가 직접 제공하는 서명 URL로 발급된다. 서명 키는 FILE_STORE_SERVICE_TOKEN에서 파생하며 URL은 만료된다(기본 900초, 최대 3600초).

text
PUT  /api/file-store/v1/local-uploads/<assetId>?expires=<epoch-ms>&signature=<hex>   # 본문 = 원본 바이트, 201 반환
GET  /api/file-store/v1/local-objects/<assetId>?expires=<epoch-ms>&signature=<hex>   # 스트림 반환
  • 서명·만료가 유효하지 않으면 401 { "error": "invalid_signature" } 다. 두 경로 모두 서비스 토큰 인증을 쓰지 않고 URL 서명만으로 통과한다.
  • 업로드는 PENDING_SCAN 상태 파일에만 허용하고(활성 파일 덮어쓰기 금지), 소비 정책(maxBytes·allowedContentTypes·writeEnabled, HTML/JS/SVG 차단)을 그대로 적용한다. 바이트 기록 후 소비자는 5. 업로드 완료를 호출해 크기·체크섬을 확정한다.
  • base 주소는 FILE_STORE_PUBLIC_URL에서 온다. 운영에서는 필수이고(미설정 시 업로드 불가), 개발에서는 http://127.0.0.1:<PORT>로 대체한다.
  • CLOUDFLARE_R2·AMAZON_S3·S3_COMPATIBLE은 기존대로 presigned URL을 쓴다(서명 URL은 LOCAL_VOLUME 전용).

References ​