Skip to content

API and Authentication

kimhoeyun edited this page Sep 8, 2026 · 4 revisions

API and Authentication

공개 API의 확인 방법과 사용자·내부 시스템 인증 경계를 설명합니다. 전체 요청·응답 스키마는 실행 중인 Swagger에서 확인합니다.

API 확인 기준

실행 중인 서버의 다음 주소를 API 계약 기준으로 사용합니다.

  • Swagger UI는 /swagger-ui/index.html입니다.
  • OpenAPI JSON은 /v3/api-docs입니다.
  • 운영 Swagger는 api.cchaksa.com에서 확인합니다.

Controller와 controller/docs 아래의 Swagger 인터페이스가 함께 API 문서를 구성합니다. Wiki에는 전체 요청·응답 스키마를 복제하지 않습니다.

API 영역

영역 주요 경로 인증
Health /health, /actuator/health 공개
사용자 로그인 POST /api/users/signin 공개
Token 갱신 POST /api/auth/refresh 공개
사용자 /api/users/me, /api/users/delete Bearer JWT
포털 연동 POST /portal/link, /portal/link/jobs/** Bearer JWT
학사 /api/academic/**, /api/semester/** Bearer JWT
학생 /api/student/** Bearer JWT
졸업 /api/graduation/** Bearer JWT
강의평가 /api/lecture-evaluations/** Bearer JWT
Worker 콜백 POST /internal/scrape-results HMAC Header
dev 테스트 /api/admin/** 일부 endpoint별 정책 확인

졸업 진행도 응답

GET /api/graduation/progressdata.analysisType은 진단 기준을 나타냅니다.

  • REGULAR: 일반 재학생의 영역별 졸업 진행도를 graduationProgress로 반환합니다.
  • TRANSFER: 편입생의 부분 진단을 transferProgress로 반환합니다. 총 취득학점, 편입 인정학점, GPA, 지정과목 이수 상태를 포함하며 자동으로 확인할 수 없는 요건은 manualReviewReasons에 표시합니다.

편입생의 analysisStatus는 현재 MANUAL_REVIEW_REQUIRED이며, 이는 API 오류가 아니라 학교 확인이 필요한 졸업요건이 남아 있다는 의미입니다. 전체 필드와 enum은 실행 중인 /v3/api-docs를 기준으로 확인합니다.

편입 영역 비교 변경 사항 — PR #343.

PR #343에 포함된 계약입니다. 운영 반영 여부는 PR과 배포 이력을 확인합니다.

  • 3학년 편입생은 편입연도에서 2년 전의 학과별 일반 학생 요건을 적용합니다. 2026년 편입이면 2024학번 기준입니다.
  • transferProgress.areas의 전핵·전선은 각각 기준학점의 50%와 실제 취득학점을 비교합니다. requiredCredits는 소수점 기준을 보존하며, 예를 들어 19학점의 절반은 9.5학점입니다.
  • 전핵 필수과목 목록은 판정하지 않습니다. requiredCourses는 기존 응답 호환을 위해 빈 목록으로 유지합니다. 전취 등 다른 영역은 EARNED_ONLY이며 전핵에 합산하지 않습니다.
  • 기준이 있으면 COMPARISON, 해당 영역의 기준이 없거나 복수전공 정책이 미확정이면 UNAVAILABLE입니다. 기준은 있지만 개인 학점이 미확인이면 COMPARISON 상태에서 학점·충족 여부를 null로 반환합니다.
  • designatedEarnedCredits는 실제 이수한 지정과목의 코드별 학점 합계입니다. 통과 성적이 있고 개인 학점만 없으면 이수 상태는 COMPLETED, 합계는 null과 COURSE_DATA_INCOMPLETE를 반환합니다. 스냅샷 미수신은 SNAPSHOT_NOT_RECEIVED로 구분합니다.
  • 두 영역을 충족해도 전체 졸업 자격을 확정하지 않으므로 analysisStatus=MANUAL_REVIEW_REQUIRED를 유지합니다.

OIDC 로그인

POST /api/users/signin 요청은 다음 정보를 받습니다.

  • providerKAKAO 또는 APPLE입니다.
  • id_token은 Provider가 발급한 ID Token입니다.
  • nonce는 로그인 요청에서 사용한 값입니다.

백엔드는 Provider 서명, iss, aud, 만료 시간, nonce를 검증합니다. 성공하면 자체 Access Token과 Refresh Token을 발급하고 포털 연동 여부를 반환합니다.

Kakao audience는 APP_KEY와 선택 값 APP_NATIVE_KEY를 사용합니다. Apple audience는 APPLE_CLIENT_ID와 선택 목록 APPLE_ALLOWED_CLIENT_IDS를 사용합니다.

OIDC 오류는 T01부터 T09를 확인합니다. 특히 T06은 앱 키와 Token의 aud가 다를 때 발생합니다.

Access Token

보호 endpoint는 다음 Header를 요구합니다.

Authorization: Bearer <access-token>

Access Token의 subject는 사용자 UUID입니다. Filter는 Token을 파싱하고 사용자 정보를 불러와 Security Context를 만듭니다.

  • Header가 없으면 보호 endpoint에서 A05 AUTHENTICATION_REQUIRED입니다.
  • 만료되거나 잘못된 Token은 401입니다.
  • 인증은 됐지만 권한이 없으면 C04 FORBIDDEN403입니다.
  • Bearer 접두사가 정확해야 합니다.

Token 원문은 로그, Issue, Sentry tag에 남기지 않습니다.

Refresh Token

POST /api/auth/refresh는 Body의 refreshToken으로 Token을 재발급합니다. 저장된 세션과 Token이 맞지 않으면 T11, 저장된 Token이 없으면 T12입니다.

Refresh Token을 Access Token 대신 Authorization Header에 넣지 않습니다. Access와 Refresh의 만료 시간은 각각 JWT_ACCESS_EXPIRATION, JWT_REFRESH_EXPIRATION으로 관리합니다.

내부 콜백 인증

/internal/scrape-results는 공개 URL이지만 HMAC 검증을 통과해야 처리됩니다.

X-Timestamp: <unix timestamp>
X-Signature: <hmac signature>
X-Callback-Attempt: <positive integer>
X-Request-Id: <worker request id>

X-Callback-AttemptX-Request-Id는 진단용이며, Timestamp와 Signature는 필수입니다. 허용 시각 차이는 SCRAPING_CALLBACK_ALLOWED_SKEW_SECONDS로 관리합니다.

서명 오류를 확인할 때도 Signature 원문이나 HMAC Secret을 공유하지 않습니다. 로그의 encoding, length, hash, timestamp delta만 사용합니다.

공통 응답과 오류

성공 응답은 SuccessResponse를 사용합니다. 실패 응답은 ErrorCode의 code, message, HTTP status를 기준으로 처리합니다.

클라이언트는 HTTP status만 보지 말고 오류 code를 함께 기록해야 합니다. 예를 들어 포털 작업은 모두 서버 오류처럼 보여도 C10, C16, C17, C18의 확인 지점이 다릅니다.

CORS

  • local은 모든 Origin을 허용합니다.
  • devprod는 코드에 등록된 척척학사 Origin과 localhost를 허용합니다.
  • OPTIONS 요청은 공개됩니다.

CORS 변경은 SecurityConfig의 프로필별 설정을 확인하고 실제 Origin으로 preflight를 재현합니다. 브라우저 오류만 보고 백엔드 인증 문제로 판단하지 않습니다.

Clone this wiki locally