Skip to content

feat: Refresh Token 회전·로그아웃과 안전한 데모 인증 기반 구현 - #35

Merged
hywznn merged 13 commits into
mainfrom
feat/4-auth-refresh-logout
Jul 22, 2026
Merged

feat: Refresh Token 회전·로그아웃과 안전한 데모 인증 기반 구현#35
hywznn merged 13 commits into
mainfrom
feat/4-auth-refresh-logout

Conversation

@hywznn

@hywznn hywznn commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

왜 필요한가요?

Closes #4

PR #33에서 구현한 로그인·JWT·사업장 권한 기반 위에 Access Token 재발급과 로그아웃을 완성합니다. Refresh Token 재사용·동시 요청·쿠키 보안 정책을 명시하지 않으면 탈취된 토큰이나 중복 재발급이 새 로그인 상태를 계속 만들 수 있고, 데모 환경의 초기 계정도 안전하게 준비하기 어렵습니다.

무엇이 바뀌나요?

  • API·도메인·DB 변경:
    • POST /api/v1/auth/refresh: HttpOnly 쿠키 전용 Refresh Token rotation
    • POST /api/v1/auth/logout: token family 폐기와 멱등 204
    • 사용·만료·폐기·알 수 없는 Refresh Token은 외부에 동일한 401 INVALID_REFRESH_TOKEN 반환
    • 원본 Refresh Token은 응답 JSON·DB·일반 로그에 저장하지 않고 SHA-256 hash만 사용
    • 기존 V2 Auth schema를 그대로 사용하며 새 migration은 없음
  • 권한·보안 변경:
    • token family 단위 비관적 잠금과 재사용 탐지 시 family 전체 폐기
    • CSRF/Origin 보호 전에는 Refresh 쿠키의 SameSite=None을 금지하고 Strict·Lax만 허용
    • 로그아웃은 다른 로그인 family를 폐기하지 않음
    • 오류 응답도 삭제 쿠키와 Cache-Control: no-store 반환
  • 감사·운영 변경:
    • 로그인·재발급·재사용·family 폐기·로그아웃의 privacy-safe typed event와 AuthAuditPort 정의
    • 현재 adapter는 request_id가 포함된 안전한 진단 로그이며 append-only 영구 저장은 #11에서 연결
    • 기본 비활성인 opt-in 데모 Company·ADMIN Seed 추가
    • Seed 비밀번호는 환경 Secret이 필수이며 BCrypt hash만 저장하고 기존 계정을 덮어쓰지 않음
  • 문서 변경:
    • OpenAPI, README, ADR-0002, .env.example에 canonical path, cookie 계약, single-flight, JWT 만료 정책과 Seed 방법 반영

어떻게 검증했나요?

  • ./gradlew clean test
  • ./gradlew build
  • Health·OpenAPI 애플리케이션 통합 테스트
  • 정상 로그인·재발급·로그아웃
  • 누락·변조·만료·폐기·재사용 토큰
  • VIEWER 쓰기 권한 거부
  • 다른 사업장 리소스 은닉
  • 로그아웃 멱등성과 로그인 family 격리
  • 데모 Seed 멱등성·비밀번호 hash 저장
  • GitHub Actions의 실제 PostgreSQL 동시성 테스트

로컬 전체 테스트와 build가 통과했습니다. 로컬에는 PostgreSQL/Docker가 없어 PostgreSQL 전용 테스트는 skip됩니다. GitHub Actions의 실제 PostgreSQL 환경에서 동일 토큰 동시 요청이 정확히 200 1건 + 401 1건인지 검증했고 통과했습니다.

보안·개인정보

  • DTO·로그·AI 입력에 불필요한 개인정보가 없습니다.
  • JWT, Refresh Token 원문, API Key, 비밀번호를 저장소와 로그에 남기지 않습니다.
  • 사용자 조회는 user_id + company_id를 함께 검증합니다.
  • 인증 실패가 계정·토큰 존재 여부를 외부에 구분해 주지 않습니다.
  • Auth 보안 이벤트에는 안전한 ID와 action·시각만 전달합니다.
  • ADR-0002의 canonical API·tenant·오류 계약을 따릅니다.
  • Server에 Prompt Builder·Provider SDK·모델 routing을 추가하지 않았습니다.

API·DB·운영 영향

  • Client는 로그인·재발급·로그아웃에 credentials: "include"를 사용해야 합니다.
  • 여러 API가 동시에 401을 받아도 Refresh 요청은 single-flight로 한 번만 보내야 합니다.
  • 로그아웃 뒤 기존 stateless Access Token은 기본 TTL 기준 최대 15분 유효하므로 Client가 즉시 삭제해야 합니다.
  • DB migration은 추가하지 않았고, PR #33의 V2 refresh_token 제약과 index를 사용합니다.
  • 새 환경변수 이름만 .env.example에 추가했으며 실제 Seed 비밀번호는 포함하지 않았습니다.
  • 롤백은 이 PR을 revert하고 DEMO_SEED_ENABLED=false를 유지하면 됩니다. 새 migration이 없어 DB rollback은 필요하지 않습니다.

응답 예시

{
  "access_token": "<redacted-jwt>",
  "token_type": "Bearer",
  "expires_in_seconds": 900,
  "expires_at": "2026-07-22T01:15:00Z"
}

새 Refresh Token은 위 JSON에 포함되지 않고 Set-Cookie로만 전달됩니다.

hywznn added 11 commits July 22, 2026 19:03
공통 오류 계약을 ApiErrorCode interface로 분리하고 로그인 오류를 AuthErrorCode로 이동했습니다. 외부 오류 code와 HTTP 응답은 그대로 유지합니다.
Refresh Token을 일회성으로 사용하고 같은 token family 안에서 새 토큰으로 교체합니다.

재사용·만료·폐기·비활성 계정은 동일한 401 응답으로 처리하며, 재사용 탐지 시 해당 token family 전체를 폐기하도록 구성했습니다.
Refresh Token cookie가 없거나 유효하지 않아도 로그아웃은 동일한 204로 응답합니다.

알려진 토큰은 같은 token family 전체를 폐기하고 브라우저 쿠키를 즉시 만료시켜 반복 호출에도 안전하게 처리합니다.
CSRF 보호가 비활성화된 MVP에서는 Refresh Token 쿠키의 SameSite=None 설정을 허용하지 않습니다.

Strict 또는 Lax만 사용하도록 제한하고, 삭제 쿠키가 발급 쿠키와 동일한 보안 속성을 유지하는지 검증했습니다.
정상 회전, 이전 토큰 재사용, token family 전체 폐기, 계정·사업장 비활성화, 로그아웃 멱등성과 로그인 family 격리를 통합 테스트합니다.

누락·변조·알 수 없는 토큰은 동일한 401과 삭제 쿠키를 반환하며, 로그아웃 뒤 기존 Access Token은 만료 전까지 유효한 정책도 명시적으로 검증합니다.
OpenAPI와 README에 cookie-only 재발급, 멱등 로그아웃, INVALID_REFRESH_TOKEN 응답과 쿠키 삭제 계약을 반영합니다.

SameSite 정책, Client single-flight, 로그아웃 후 기존 Access Token의 최대 유효 시간도 ADR과 초보자용 흐름에 함께 설명했습니다.
Refresh Token 검증 실패 응답에도 삭제 쿠키와 함께 Cache-Control: no-store, Pragma: no-cache를 반환합니다.

브라우저나 중간 캐시가 인증 오류와 토큰 관련 응답을 저장하지 않도록 실제 응답과 OpenAPI 계약을 함께 검증했습니다.
동일 Refresh Token 요청 두 개가 실제 Repository 조회 시점에 겹치도록 조정하고 PostgreSQL family lock 동작을 검증합니다.

정확히 한 요청만 회전하며, 다른 요청의 재사용 탐지 뒤 교체 토큰까지 포함한 family 전체가 폐기되는지 CI에서 확인합니다.
Refresh Token 생성 바이트 수와 입력 검증 형식을 하나의 정책으로 묶어 서로 다른 값으로 변경되는 위험을 줄입니다.

URL-safe Base64 무패딩 형식의 허용·거부 경계를 단위 테스트로 고정했습니다.
로그인 성공·실패, 토큰 회전·거부·재사용, family 폐기와 로그아웃을 typed event로 정의하고 안전한 식별자만 기록합니다.

현재는 request_id가 포함된 진단 로그 adapter를 사용하며, append-only 영구 저장은 감사 모듈 소유 이슈 #11에서 이 port에 연결합니다.
명시적으로 활성화한 환경에서만 데모 사업장과 ADMIN 계정을 Flyway 이후 한 번 생성합니다.

비밀번호 기본값을 두지 않고 BCrypt hash만 저장하며, 기존 계정을 덮어쓰지 않는 멱등 동작과 Secret 비노출을 테스트·README·환경변수 예시에 반영했습니다.
@hywznn hywznn added the area:server Spring Boot API·도메인·DB·tenant·Task Workflow 영역; Prompt·모델·Provider 구현 제외 label Jul 22, 2026
@hywznn hywznn self-assigned this Jul 22, 2026
@hywznn hywznn added priority:P0 MVP 진행을 막는 최우선 핵심 작업 security:privacy 개인정보·접근권한·토큰·보안 영향이 있는 작업 status:in-progress 담당자가 현재 구현 중인 작업 type:feature 사용자 또는 Agent가 사용하는 기능 개발 labels Jul 22, 2026
이미 생성된 데모 ADMIN이 있어도 연결된 Company가 실제로 존재하고 활성 상태인지 다시 확인합니다.

비활성 사업장을 정상 Seed로 오인하지 않도록 회귀 테스트를 추가했습니다.
@hywznn hywznn added status:in-review 구현을 마치고 리뷰 또는 병합을 기다리는 작업 and removed status:in-progress 담당자가 현재 구현 중인 작업 labels Jul 22, 2026
동시 요청이 lock을 기다리기 전에 시각을 잡아 새 교체 토큰보다 이른 revoked_at을 기록하던 PostgreSQL 제약 위반을 수정합니다.

Refresh와 Logout 모두 family 조회·잠금 이후에 현재 시각을 읽도록 바꾸고 호출 순서 회귀 테스트를 추가했습니다.
@hywznn
hywznn marked this pull request as ready for review July 22, 2026 10:49
@hywznn
hywznn requested a review from chaeliki July 22, 2026 10:49
@hywznn

hywznn commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

로그인 기능을 최대한 고도화 해봤습니다 한번 나중에 어디다가 써먹어보려구여

Refresh Token Rotation, Token Family, 재사용 탐지(Re-use Detection), HttpOnly 쿠키, 토큰 해시 저장, 동시 재발급 제어(Single-flight/동시성 제어)까지 포함되어 있어, JWT 인증 시스템에서 중요한 보안 요구사항들을 종합적으로 반영한 구현

@hywznn
hywznn merged commit 32655ea into main Jul 22, 2026
1 check passed
@hywznn
hywznn deleted the feat/4-auth-refresh-logout branch July 22, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:server Spring Boot API·도메인·DB·tenant·Task Workflow 영역; Prompt·모델·Provider 구현 제외 priority:P0 MVP 진행을 막는 최우선 핵심 작업 security:privacy 개인정보·접근권한·토큰·보안 영향이 있는 작업 status:in-review 구현을 마치고 리뷰 또는 병합을 기다리는 작업 type:feature 사용자 또는 Agent가 사용하는 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Auth & Security] JWT 인증·사업장 권한·멀티테넌시 구현

1 participant