Conversation
- `AuthInterceptorTest`: 선제적 토큰 재발급(reissue) 및 응답 봉투의 `expiresIn` 값을 통한 만료 기한 학습 로직 검증 - `TokenAuthenticatorTest`: 401 Unauthorized 응답 발생 시 사후 대응, 최대 재시도 제한 및 세션 정리 계약 검증 - `AccessTokenExpiryTrackerTest`: 토큰 만료 기한(deadline) 산식 및 60초 임계값 경계 조건 테스트 - `TokenReissueCoordinatorTest`: 다중 경로 요청 시 중복 재발급을 방지하는 단일 비행(single-flight) 조정 로직 검증 - `BaseResponseExpiresInContractTest`: 서버 응답의 `expiresIn` 필드 유무에 따른 JSON 디코딩 호환성 테스트 - 테스트 시나리오 구성을 위한 `FakeAuthRepository` 및 `RecordingChain` 구현 추가
- 액세스 토큰의 만료 시점(deadline)을 단조 시계(`SystemClock.elapsedRealtime`) 기반으로 추적하는 `AccessTokenExpiryTracker` 추가 - 선제 갱신(`AuthInterceptor`)과 401 사후 대응(`TokenAuthenticator`) 간의 중복 회전을 방지하기 위해 단일 락(Single-flight)을 보장하는 `TokenReissuer` 구현 - `AuthInterceptor`에서 성공 응답 봉투의 `expiresIn` 필드를 추출하여 토큰 수명을 기록하고, 만료 임박 시 선제적으로 reissue를 수행하는 로직 추가 - `BaseResponse` 공통 응답 모델에 `expiresIn` 필드 추가 및 관련 상수 정의 - `TokenAuthenticator`가 `TokenReissuer`를 경유하도록 리팩터링하고, `responseCount` 및 요청 재생성 로직(`withBearer`) 개선 - 단위 테스트 실행 시 `android.util.Log` 등 Android API 스텁의 기본값 반환을 위해 `isReturnDefaultValues` 설정 활성화 - 기존 `TokenReissueCoordinator`를 `TokenReissuer`로 대체 및 관련 테스트 코드 최신화
- `expiresIn` 수신이 특정 엔드포인트에 의존하며, 사용자 동선에 따라 기록되지 않을 수 있음을 명시 - 기록이 없을 경우 선제 갱신이 생략되고 401 안전망을 통해 만료 처리가 수행됨을 설명 - 향후 근본적인 해결 방향(#410)에 대한 참고 정보 추가
- `BaseResponse` 공통 응답 봉투 최상위에 정의되어 있던 `expiresIn` 필드 제거 - `LoginData` 및 `ReissueData` DTO 내부로 `expiresIn` 필드 이동 (#410 서버 계약 변경 대응) - `AuthInterceptor`에서 모든 성공 응답 본문을 peek 하여 `expiresIn`을 추출하던 횡단 관심사 로직 제거 (성능 개선) - `AuthRepositoryImpl`(로그인) 및 `TokenReissuer`(토큰 회전)에서 응답 데이터 수신 시 직접 `AccessTokenExpiryTracker`에 만료 시각을 기록하도록 변경 - `TokenBundle` 모델 및 `AuthMapper`에 `expiresIn` 필드 추가 및 매핑 로직 반영 - 공통 봉투 기반의 만료 테스트(`BaseResponseExpiresInContractTest`)를 삭제하고, DTO 기반의 신규 계약 테스트(`AuthDtoExpiresInContractTest`) 추가 - `AuthInterceptorTest` 및 `TokenReissuerTest`에서 변경된 만료 정보 기록 흐름에 맞춰 테스트 케이스 현행화
- `AuthInterceptor`가 매 요청 직전 호출하여 현재 시각 기준으로 만료 임박 여부를 매번 재계산함을 명시 - 만료 시각 정보가 없을 경우(null)의 동작 방식(선제 갱신 스킵 및 401 발생 시 사후 대응)에 대한 설명 구체화
This was referenced Jun 21, 2026
koongmai
reviewed
Jun 29, 2026
koongmai
left a comment
Contributor
There was a problem hiding this comment.
#411 로그아웃 요청도 선제 reissue 대상이라 새 refresh token이 서버에 남을 수 있음
core/network/.../AuthInterceptor.kt:59에서 만료 임박이면 모든 인증 요청에 대해 선제 reissue를 수행하고, core/data/.../AuthRepositoryImpl.kt:117의 logout은 그 전에 읽어둔 기존 refresh token으로 로그아웃 요청을 보냅니다.
토큰 만료 60초 이내에 logout하면 interceptor가 먼저 새 refresh token을 발급/저장한 뒤, 서버에는 old refresh token만 전달될 수 있어서 “로컬 로그아웃은 됐지만 서버 세션 폐기는 안 된” 상태가 됩니다. logout endpoint는 선제 reissue 제외하거나, logout 시 최신 refresh token 기준으로 요청하도록 고쳐야 합니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📌𝘐𝘴𝘴𝘶𝘦𝘴
Closes #408 — feat(core-network): expiresIn 활용 — 액세스 토큰 만료 전 선제 reissue
연계 #410(BE) — expiresIn 을 발급 응답으로 이동 (2026-06-20 서버 반영·실측 확인)
📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯
data의expiresIn수신 —LoginData(Default/Social)·ReissueData에 필드 추가. BE chore(backend): expiresIn 을 목록 응답에서 토큰 발급 응답(로그인·리이슈)으로 이동 요청 #410 으로 expiresIn 이 봉투 최상위에서 발급(로그인/리이슈) 응답data안(RFC 6749 §5.1 의 access_token 형제 자리)으로 이동했고,BaseResponse봉투 필드·EXPIRES_IN_SERIAL_NAME상수는 제거 (df2bf92)token/AccessTokenExpiryTracker— 만료 deadline 을 in-memory 보관 (elapsedRealtime 기준, 임계 60초, 의도적 비영속 — 재시작 후엔 다음 발급/회전이 다시 기록)token/TokenReissuer— 선제·401 두 reissue 경로의 단일 비행(single-flight) 락. "기대 토큰 vs 저장 토큰" 재확인으로 이중 rotateToken 경쟁 차단. 회전 성공 시 발급 응답의 expiresIn 으로 deadline 기록(미동봉이면 clear), 실패 시 clearAuthRepositoryImpl(default/kakao/google 3개 진입점), 회전 성공은TokenReissuer가 각각 발급 응답 expiresIn 을record. 과거 모든 성공 응답을 peek 하던 횡단 수집(AuthInterceptor.recordExpiresIn)은 제거AuthInterceptor— 만료 임박 시 요청 전 선제 reissue(실패는 기존 401 안전망에 위임, clearSession 안 함). 응답 peek·json의존 제거로 토큰 부착·선제 갱신 책임만 남김TokenAuthenticator— 회전을 TokenReissuer 경유로 전환,withBearer/responseCount를 OkHttp 공식 recipes 형태의 확장으로 정리data.expiresIn수신 계약(AuthDtoExpiresInContractTest3종). 인터셉터 봉투 기록 가드는 peek 제거로 함께 제거 (9fb8fb0, df2bf92)📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵
UI 변경 없음 — core:network 토큰 갱신 로직 전용.
💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴
BaseResponse.kt겹침. feat(afternote): 수신자 본인확인 이메일 인증 stub → 실 API 전환 (closes 407) #409 머지 후 develop 재타겟 예정 (base 필터 CI 는 재타겟+push 전까지 안 돎).POST /auth/login·POST /auth/reissue응답data에expiresIn:3600추가,GET /users/receivers목록에선 제거(롤백) 확인. 이에 맞춰 수신 경로를 "모든 응답 봉투 peek" → "발급 응답data기록"으로 교체 — 회전 메커니즘(tracker·TokenReissuer·선제 분기·401 전환)은 채널과 무관하게 유지. 위치는 봉투 최상위가 아니라data안이라 FE 가 거기서 읽는다.TokenDataSource(final + DataStore 의존) fake 셋업이 convention plugin 의존성을 건드릴 위험(throwaway)이라, ① 데이터 수신은AuthDtoExpiresInContractTest(발급 DTOdata.expiresIn디코드) ② 회전 record 는TokenReissuerTest로 가드. 로그인 record 호출 자체는 명시적 3줄 + 에뮬 실측으로 커버. repo 단위 테스트가 필요하면 후속 커밋으로 추가 가능.AuthInterceptorKDoc 명시.isReturnDefaultValues는TokenAuthenticator의 Log.e 경유 유닛 테스트용 — 공식 가이드의 last-resort 단서를 build.gradle.kts 주석에 반영.:core:network:testDebugUnitTest(27) ·:core:data:testDebugUnitTest·:app:assembleDebug·:core:network/:core:data/:core:modelktlintCheck모두 BUILD SUCCESSFUL.