Skip to content

Conversation

@Hrepay
Copy link
Member

@Hrepay Hrepay commented Jul 25, 2025

#️⃣ 관련 이슈

Resolved #293

💡작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능)

  • 토큰 재발급 시 403 응답에 대한 예외 처리가 누락되어 발생하던 문제를 수정했습니다.
  • AuthInterceptor 내 reissueProvider를 제거하고, TokenRefresher 내부의 MoyaProvider(provider)를 이용해 Swift Concurrency 기반으로 재발급 로직을 구현했습니다.
  • AuthInterceptor는 모든 네트워크 요청에 access token을 자동으로 포함시키고, 401/403 응답 시 TokenRefresher를 호출해 토큰을 재발급합니다.
  • TokenRefresher는 저장된 refresh token을 기반으로 /auth/reissue API를 호출하여 새로운 access token을 받아 저장하고, 실패 시 에러를 throw 하도록 했습니다.
  • TokenManager는 access token의 payload에서 exp 값을 추출하여 만료 여부를 판단하고, 남은 유효 시간이 2시간 이하면 재발급이 동작하게 설정했습니다. 이 로직은 앱 실행 시와 포그라운드 전환 시 자동으로 수행됩니다.
  • LoginViewController.toastMessage는 "시스템 오류"에서 "세션이 만료되었습니다"로 메시지를 수정했습니다.
  • 재발급 성공 시 발급과 동일하게 "⭐️⭐️ 재발급 완료 ⭐️⭐️" 로그를 출력해 가독성을 높였습니다.
  • Logger 파일 내 주석을 정리하고, 개발 중에 로그를 쉽게 제어할 수 있도록 제어 변수를 추가했습니다. 현재는 false로 설정되어 있습니다.
  • 제미나이 세팅을 추가했습니다.

💬리뷰 요구사항(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요
ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

현재 로직은 토큰 관련 로직 다음과 같습니다.

  • access token 만료 → refresh token으로 재발급 시도
  • refresh token까지 만료된 경우 → 인증이 필요한 요청 시에만 "세션 만료" 메시지와 함께 로그인 창으로 이동
    하지만 방학에는 장기간 앱을 사용하지 않을 사용자도 있을텐데 그렇게 되면 refresh token도 만료될 수 있습니다.
    이 경우, 홈 화면은 인증 없이 접근 가능하기 때문에 앱 실행 직후 로그인 창으로 전환되지 않고 정상 진입합니다.

Q. refresh token이 만료된 경우 앱 실행 직후 로그인 화면으로 강제 전환하는 게 나을까요? 아니면 현재처럼 인증 요청 시점에만 로그인 창으로 이동하는 게 자연스러울까요?

팀원 분들의 의견이 궁금합니다.
작업 내용 중에도 수정사항이 있다면 말씀해주세요!

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @Hrepay, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the token reissuance mechanism to enhance robustness and user experience. It introduces a new Swift Concurrency-based approach for handling token refreshes, centralizing the logic and preventing race conditions. The changes also include proactive token expiration checks and improved error handling for session management.

Highlights

  • Token Reissuance Refactor: The token reissuance logic has been completely overhauled, moving from a Moya-based reissueProvider to a new Swift Concurrency-based system. This improves asynchronous handling and reliability.
  • Centralized Token Refresh: New TokenRefresher and TokenManager classes are introduced. TokenRefresher handles the actual API call for reissuing tokens, while TokenManager manages token expiration checks and triggers refreshes.
  • Enhanced Auth Interceptor: The AuthInterceptor now correctly handles both 401 (Unauthorized) and 403 (Forbidden) HTTP status codes to trigger token reissuance attempts, addressing a previously missing exception.
  • Proactive Token Management: The TokenManager now proactively checks for access token expiration (if less than 2 hours remaining) by decoding the JWT payload. This reissuance is automatically triggered on app launch and when the app enters the foreground.
  • Improved User Feedback: The toast message for session expiration has been updated from a generic "System error" to a more specific "Session expired. Please log in again." in relevant view controllers.
  • Configurable Logging: The network logging system has been refined with a new isVerboseNetworkLoggingEnabled flag, allowing developers to easily control the verbosity of network logs during development.
  • Swift Concurrency Support: The project settings have been updated to explicitly enable SWIFT_CONCURRENCY: "complete", ensuring full support for the new concurrency model.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The code changes introduce a refactored token reissue logic using Swift Concurrency, improving thread safety and code structure with separate TokenManager and TokenRefresher components. The AuthInterceptor automatically attaches access tokens to network requests and handles token reissuance upon authentication failures.

do {
try await TokenRefresher.shared.refreshIfNeeded()
await MainActor.run { completion(.retry) }
} catch {

This comment was marked as resolved.

/// refreshToken 기반으로 accessToken 재발급 요청
private func performReissuance() async throws -> SignResponse {
try await withCheckedThrowingContinuation { continuation in
let provider = MoyaProvider<ReissueRouter>()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The MoyaProvider is instantiated within the performReissuance function. For better performance, consider instantiating it as a property of the TokenRefresher actor to avoid creating a new instance with each token refresh.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 좋은 것 같아서 채택했습니다!

@Hrepay
Copy link
Member Author

Hrepay commented Jul 25, 2025

@CJiu01 @Funital
아 그리고 Gemini Code Assist 사용하는데 있어서 .gemini/ 폴더 만들어서 한국어로 설정해달라고 하려는데 괜찮을까요?

@CJiu01
Copy link
Collaborator

CJiu01 commented Jul 27, 2025

@CJiu01 @Funital 아 그리고 Gemini Code Assist 사용하는데 있어서 .gemini/ 폴더 만들어서 한국어로 설정해달라고 하려는데 괜찮을까요?

좋습니다!

@CJiu01
Copy link
Collaborator

CJiu01 commented Jul 27, 2025

Q. refresh token이 만료된 경우 앱 실행 직후 로그인 화면으로 강제 전환하는 게 나을까요? 아니면 현재처럼 인증 요청 시점에만 로그인 창으로 이동하는 게 자연스러울까요?

제 의견은 단순히 메뉴 확인용으로 앱을 사용하는 경우도 많은 것 같아 토큰이 필요없는 경우라면 굳이 로그인하지 않아도 될 것 같습니다. 해당 부분도 안드와 통일하면 좋을 것 같은데 어떻게 생각하시나요?! @kangyuri1114 @HI-JIN2

@kangyuri1114
Copy link
Member

@CJiu01 안드도 아요처럼 현재 토큰이 만료 시에 바로 로그인 화면 이동이 아니라
토큰이 필요한 기능을 사용할떄 이동하고 있습니다!
저는 지금 로직이 좋은 것 같아요

Copy link
Collaborator

@CJiu01 CJiu01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concurrency를 아주 야무지게 사용하셨군요! 수고하셨습니다!!

홈 화면은 인증 없이 접근 가능하기 때문에 앱 실행 직후 로그인 창으로 전환되지 않고 정상 진입 -> 해당 상황은 사용자가 앱을 완전히 종료하지 않은 상태(백그라운드 상태)에서 앱 접속하는 경우에 발생하는거죠?

Comment on lines +32 to +34
defer {
isRefreshing = false
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defer 사용해서 강제할 수도 있군요!

accessToken: data.accessToken,
refreshToken: data.refreshToken
)
print("⭐️⭐️ 재발급 완료 ⭐️⭐️ – 새 accessToken:", data.accessToken)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 부분은 디버그환경일 때만 찍어주면 좋을듯요!

Comment on lines +12 to +13
/// 네트워크 로그 출력을 제어하는 플래그
private let isVerboseNetworkLoggingEnabled = false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그 출력할 때, 해당 부분을 true로 사용하면 되나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 true로 바꾸면 다시 원래대로 찍힙니다!

@Hrepay
Copy link
Member Author

Hrepay commented Aug 1, 2025

Concurrency를 아주 야무지게 사용하셨군요! 수고하셨습니다!!

홈 화면은 인증 없이 접근 가능하기 때문에 앱 실행 직후 로그인 창으로 전환되지 않고 정상 진입 -> 해당 상황은 사용자가 앱을 완전히 종료하지 않은 상태(백그라운드 상태)에서 앱 접속하는 경우에 발생하는거죠?

넵 맞습니다!
말씀하신 디버그랑 #296 병합 진행했고, 승인 주시면 오늘 안에 업데이트 올리겠습니다!

@Hrepay Hrepay merged commit c03351a into develop Aug 1, 2025
@Hrepay Hrepay deleted the fix/#293 branch August 1, 2025 06:08
@CJiu01
Copy link
Collaborator

CJiu01 commented Aug 1, 2025

Concurrency를 아주 야무지게 사용하셨군요! 수고하셨습니다!!
홈 화면은 인증 없이 접근 가능하기 때문에 앱 실행 직후 로그인 창으로 전환되지 않고 정상 진입 -> 해당 상황은 사용자가 앱을 완전히 종료하지 않은 상태(백그라운드 상태)에서 앱 접속하는 경우에 발생하는거죠?

넵 맞습니다! 말씀하신 디버그랑 #296 병합 진행했고, 승인 주시면 오늘 안에 업데이트 올리겠습니다!

#296 테스트 해보니 탈퇴 이후 별도 액션 없이 화면이 바뀌는 게 약간 튕기는 듯한 느낌이 들어서, 토스트 메시지까지 추가하고 해당 작업까지 업데이트 올리려 합니다! 제가 해당 작업 후, 업데이트 진행하겠습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Fix] 토큰 재발급 로직 미작동 이슈

5 participants