Skip to content

Latest commit

 

History

History
322 lines (232 loc) · 13.5 KB

File metadata and controls

322 lines (232 loc) · 13.5 KB

백엔드 개발자 문서

1. 목적

이 프로젝트는 CODEF API와 간편인증을 이용해 건강검진 데이터를 조회하는 데모 구현이다. 현재는 Spring Boot + Thymeleaf 구조지만, 실제 서비스에서는 백엔드가 CODEF 연동을 전담하고 클라이언트에는 REST API만 제공해야 한다.

백엔드 개발자의 핵심 책임은 아래와 같다.

  1. 사용자 입력 검증
  2. CODEF 1차 요청과 2차 요청 orchestration
  3. OAuth 토큰 관리
  4. 간편인증 대기 상태 관리
  5. 결과 요약, 오류 표준화, 민감정보 마스킹

2. 현재 데모의 구현 위치

3. 현재 서버 로직 요약

3.1 1차 요청

HealthCheckService.javastart()는 아래 필드로 기본 요청을 만든다.

  • id
  • userName
  • phoneNo
  • identity
  • searchStartYear
  • searchEndYear
  • loginTypeLevel (선택한 간편인증 수단 설정값)
  • telecom (PASS처럼 통신사가 필요한 인증수단에서만)

현재 구현의 간편인증 수단 매핑은 아래와 같다.

  • kakao -> loginType="5", loginTypeLevel="1"
  • pass -> loginType="5", loginTypeLevel="5", telecom 필수

실제 CODEF 호출 직전 HttpCodefGateway.java 에서 아래 공통 파라미터가 추가된다.

  • serviceType
  • organization
  • type
  • inquiryType
  • loginType

loginTypeLevel은 서비스가 인증수단별 값으로 먼저 넣고, 게이트웨이는 값이 없을 때만 fallback 설정값을 사용한다.

인증수단별 CODEF 값은 CodefProperties.javacodef.simple-auth-methods.* 설정에서 읽어온다.

3.2 추가인증 판단

현재 구현(HealthCheckService.javaisContinueTwoWay())은 아래 조건 중 하나라도 만족하면 PENDING으로 본다.

  1. 응답에 data.twoWayInfo, twoWayInfo, result.twoWayInfo 중 하나가 비어 있지 않은 객체로 존재
  2. 응답 코드가 CF-03002
  3. result.continue2Way, continue2Way, data.continue2Way 중 하나가 true

이 순서대로 평가하며, 1번이 참이면 나머지는 확인하지 않는다.

3.3 성공 판단

현재 구현(isSuccessResult())의 성공 판단 기준은 아래와 같다.

  1. resultCodeCF-00000 또는 00000
  2. resultCode"0"으로 시작
  3. 응답에 resData 필드가 존재하거나 data.resData가 존재

위 조건 중 하나라도 만족하면 SUCCESS로 본다. 단, 추가인증 판단(isContinueTwoWay)이 먼저 평가되므로, twoWayInfo가 있으면 성공 코드라도 PENDING이 우선한다.

3.4 2차 요청

poll()은 세션에 저장된 상태를 읽어 아래 값을 추가해 재호출한다.

  • is2Way=true
  • twoWayInfo (1차 응답에서 추출한 값)
  • simpleAuth=1twoWayInfo.method"simpleauth"(대소문자 무시)일 때만 추가

simpleAuth 방식과 일반 2Way 방식의 차이

현재 데모에서는 인증 방식에 따라 polling 동작이 다르다.

구분 일반 2Way simpleAuth
polling 방식 자동 (3초 간격 반복) 수동 (사용자가 "결과 확인" 버튼 클릭)
이유 백그라운드에서 인증 상태가 바뀜 승인 완료 전 반복 요청 시 CODEF가 인증 실패(CF-12872)로 처리할 수 있음
클라이언트 UX 로딩 애니메이션 + 자동 전환 안내 문구 + 수동 확인 버튼

이 분기는 컨트롤러에서 twoWayInfo.method를 확인해 simpleAuthPending 플래그로 클라이언트에 전달하며, 클라이언트는 이 값에 따라 auto-poll 또는 manual-poll을 선택한다.

3.5 OAuth 토큰 관리

현재 구현(HttpCodefGateway.java)의 토큰 관리 패턴은 아래와 같다.

  • 토큰 캐싱: 발급받은 access_token을 인스턴스 변수에 보관하고, 만료 전까지 재사용한다.
  • 만료 안전 마진: CODEF가 내려주는 expires_in에서 60초를 빼서 만료 시점을 앞당긴다. 이를 통해 네트워크 지연으로 만료 직전 토큰을 사용하는 상황을 방지한다.
  • 401 재시도: API 호출 시 401 Unauthorized가 발생하면 캐시된 토큰을 삭제하고 새 토큰을 발급받아 한 번 더 시도한다.
  • 동시성 제어: volatile 변수와 synchronized 블록을 이용한 double-checked locking으로 다중 스레드 환경에서의 중복 발급을 방지한다.
  • 인증 방식: OAuth 토큰 요청은 grant_type=client_credentials, scope=read, Basic Auth(clientId/clientSecret)를 사용한다.
  • 상품 요청 바디: easycodef-java와 동일하게 요청 JSON 전체를 URL-encode한 문자열로 전송하고, Accept: application/json 헤더를 사용한다.

실서비스에서는 이 패턴을 유지하되, 다중 WAS 환경이면 Redis 등 공유 저장소에 토큰을 캐싱하는 방안을 고려할 수 있다.

3.6 최종 상태

상태 모델은 FlowStatus.java 기준으로 아래 네 가지다.

  • SUCCESS
  • PENDING
  • FAILED
  • TIMEOUT

4. 실제 서비스에서 권장하는 분리 구조

flowchart LR
    Client["Client App"] --> Api["Health Check API"]
    Api --> Orchestrator["HealthCheckService"]
    Orchestrator --> Store["Request State Store"]
    Orchestrator --> Codef["CODEF Gateway"]
    Codef --> OAuth["CODEF OAuth"]
Loading

권장 책임 분리는 아래와 같다.

클라이언트

  • 사용자 입력 수집
  • 조회 시작 요청
  • 상태 polling
  • 결과 표시

백엔드

  • 입력 검증
  • 요청 ID 발급
  • CODEF OAuth 토큰 발급 및 캐시
  • 1차 요청 / 2차 요청 분기
  • pending 상태 저장
  • 오류 코드 정규화
  • 감사 로그, 마스킹, 운영 보안

5. 권장 API 계약

현재 데모는 세션 기반 HTML 렌더링이다. 실제 서비스는 아래처럼 JSON API로 전환하는 편이 적절하다. 아래 계약은 실서비스 전환을 위한 권장안이며, 현재 데모가 동일한 REST 엔드포인트를 이미 제공하는 것은 아니다.

5.1 조회 시작

POST /api/health-check/requests

요청:

{
  "userName": "홍길동",
  "phoneNo": "01012345678",
  "identity": "19900101",
  "searchStartYear": 2023,
  "searchEndYear": 2024,
  "authMethodKey": "pass",
  "telecomCode": "0"
}

응답:

{
  "requestId": "4de0d5c0-b8fb-4a36-9cb4-4aa0f6db4d0f",
  "status": "PENDING",
  "message": "추가 인증이 필요합니다.",
  "errorCode": "CF-03002",
  "pollIntervalMs": 3000,
  "summary": {
    "pending": true
  }
}

5.2 상태 조회

GET /api/health-check/requests/{requestId}

응답:

{
  "requestId": "4de0d5c0-b8fb-4a36-9cb4-4aa0f6db4d0f",
  "status": "SUCCESS",
  "message": "조회가 완료되었습니다.",
  "errorCode": "CF-00000",
  "summary": {
    "resultCode": "CF-00000",
    "message": "성공",
    "status": "SUCCESS"
  }
}

5.3 취소

필수는 아니지만 운영 UX를 위해 아래 API를 고려할 수 있다.

DELETE /api/health-check/requests/{requestId}

6. 구현 시 반드시 반영할 CODEF 규칙

codef-health-check-reference.md 기준으로 중요한 규칙은 아래와 같다.

  • 엔드포인트는 /v1/kr/public/pp/nhis-health-checkup/result
  • 조회 연도 키는 searchStartYear, searchEndYear
  • 간편인증 1차 요청은 loginType="5"
  • 1차 요청의 loginTypeLevel, telecom은 선택한 인증수단 설정에 따라 조합
  • CF-03002 또는 continue2Way=true일 때만 2차 요청으로 전환
  • 2차 요청은 is2Way=truetwoWayInfo가 필요
  • method == simpleAuth이면 2차 입력으로 simpleAuth=1
  • 간편인증 승인 전에 simpleAuth="1"을 반복 전송하면 2번까지 재시도, 3번째에 CF-12872 발생
  • 간편인증 타임아웃은 CODEF 문서 기준 4분 30초
  • 최종 성공 응답에는 twoWayInfo가 없을 수 있음
  • 최종 성공 응답의 일반 datatwoWayInfo로 오인하면 안 됨

productUrl 관련 참고: CodefProperties.java의 Java 기본값은 /v1/kr/public/pp/nhis-health-check이지만, application.yml에서 /v1/kr/public/pp/nhis-health-checkup/result로 오버라이드된다. 실제 동작에는 application.yml 값이 우선하므로 문제없지만, 혼동을 피하려면 Java 기본값도 통일하는 편이 낫다.

주요 에러 코드

코드 의미 대응
CF-00000 성공 결과 표시
CF-03002 추가인증 필요 2차 요청으로 전환
CF-12872 간편인증 반복 실패 인증 완료 전 simpleAuth="1" 3회 전송 시 발생. 재시도 불가, 처음부터 다시 시작
CF-13002 인증 오류 simpleAuth 관련 사용자 입력 오류 가능성. 상세 메시지 확인 필요
CF-00404 엔드포인트 없음 codef.product-url 설정 확인

7. 세션 기반 구현에서 requestId 기반 구현으로 바꿔야 하는 이유

현재 데모는 HttpSession에 아래 정보를 저장한다.

  • requestId
  • 1차 요청 파라미터
  • twoWayInfo
  • 시작 시각
  • 마지막 결과

이 방식은 데모에는 충분하지만 실제 서비스에는 한계가 있다.

  • 다중 WAS 환경에서 세션 일관성이 필요하다.
  • 모바일 앱/SPA와 결합하기 어렵다.
  • 브라우저 세션 종료 시 복구가 어렵다.
  • 운영 로그와 사용자 요청 추적이 불명확해진다.

실서비스에서는 DB 또는 Redis 등에 requestId 단위 상태를 저장하는 편이 낫다.

8. 권장 데이터 모델

예시:

필드 설명
requestId 외부 노출용 조회 요청 ID
status PENDING, SUCCESS, FAILED, TIMEOUT
requestPayloadMasked 마스킹된 요청값
twoWayInfo CODEF 2차 요청용 상태
resultCode CODEF 결과 코드
resultMessage CODEF 결과 메시지
resultSummary UI 전달용 요약
startedAt 조회 시작 시각
updatedAt 마지막 갱신 시각
expiresAt polling 종료 시각

9. 간편인증 설정 구조

현재 데모는 아래 설정 구조로 인증수단을 확장한다.

  • codef.simple-auth-methods.<methodKey>.display-name
  • codef.simple-auth-methods.<methodKey>.login-type-level
  • codef.simple-auth-methods.<methodKey>.requires-telecom
  • codef.simple-auth-methods.<methodKey>.telecom-options[]

기본값은 kakao, pass 두 가지 키를 제공한다.

  • kakao는 기본 loginTypeLevel=1, 통신사 선택 없음
  • pass는 통신사 선택이 필요하며, 기본 telecom-options는 공식 문서 기준 0=SKT(SKT 알뜰폰), 1=KT(KT 알뜰폰), 2=LG U+(LG U+ 알뜰폰)을 사용함

loginTypeLevel이 비어 있으면 컨트롤러와 서비스가 CODEF 호출 전에 검증 오류로 요청을 차단한다.

9. 보안 체크리스트

  • CODEF client id/secret은 소스코드가 아니라 Secret Manager 또는 환경변수로 주입
  • OAuth access token은 서버에서만 관리
  • 휴대폰 번호, 생년월일, 주민등록 관련 값은 로그에 원문 저장 금지
  • 결과 원문(rawResult)은 운영 환경에서 사용자 응답에 그대로 포함하지 않는 편이 안전
  • 요청/응답 로그는 requestId 기준으로 추적
  • 내부 운영자 화면이 없다면 민감 원문은 저장하지 않는 편이 낫다

공개 저장소 기준으로 application.yml 에는 동작 가능한 credential 기본값을 두지 않는다. 실제 프로젝트에서는 환경변수 또는 별도 비밀 저장소를 통해 주입해야 한다.

10. 운영 체크리스트

  • polling 기본 주기 정의
  • 최대 대기 시간 정의
  • 중복 요청 방지 정책
  • 동일 사용자 다건 요청 정책
  • 재시도 가능 오류와 불가 오류 분리
  • CODEF 장애 시 fallback 메시지 정의
  • 감사 로그와 장애 모니터링 연결

11. 권장 구현 순서

  1. HealthCheckController의 화면 반환 로직을 REST API로 분리
  2. HealthCheckService의 세션 의존 로직을 request state repository로 이동
  3. HttpCodefGateway는 외부 연동 전용 컴포넌트로 유지
  4. API 응답 DTO를 클라이언트 계약 기준으로 재정의
  5. 운영 보안 정책과 로그 마스킹 규칙 적용

12. 데모를 실서비스에 적용할 때 주의할 점

현재 코드는 데모 목적상 구조가 단순하다. 실서비스에서는 "클라이언트가 상태를 보여주고, 백엔드가 CODEF 상태 머신을 관리한다"는 원칙을 지키는 것이 중요하다. 특히 pending 상태와 twoWayInfo 보관은 백엔드 책임으로 고정하는 편이 안전하다.