From 77213bcc9dae9b887a1ab1480c54da48ae5557e8 Mon Sep 17 00:00:00 2001 From: casperjr Date: Tue, 2 Sep 2025 11:09:07 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[FIX]:=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EC=9A=94=EC=B2=AD=EC=8B=9C=20=EB=AC=B4?= =?UTF-8?q?=ED=95=9C=EB=A3=A8=ED=94=84=20=EB=B0=A9=EC=A7=80=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/kuit/ourmenu/data/service/AuthService.kt | 5 ----- .../com/kuit/ourmenu/data/service/TokenService.kt | 13 +++++++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/kuit/ourmenu/data/service/TokenService.kt diff --git a/app/src/main/java/com/kuit/ourmenu/data/service/AuthService.kt b/app/src/main/java/com/kuit/ourmenu/data/service/AuthService.kt index 798ed8b3..4fbd8513 100644 --- a/app/src/main/java/com/kuit/ourmenu/data/service/AuthService.kt +++ b/app/src/main/java/com/kuit/ourmenu/data/service/AuthService.kt @@ -7,7 +7,6 @@ import com.kuit.ourmenu.data.model.auth.request.SignupRequest import com.kuit.ourmenu.data.model.auth.response.CheckKakaoEmailResponse import com.kuit.ourmenu.data.model.auth.response.EmailResponse import com.kuit.ourmenu.data.model.auth.response.LoginResponse -import com.kuit.ourmenu.data.model.auth.response.ReissueTokenResponse import com.kuit.ourmenu.data.model.auth.response.SignupResponse import com.kuit.ourmenu.data.model.base.BaseResponse import retrofit2.http.Body @@ -27,10 +26,6 @@ interface AuthService { @Body request: LoginRequest ): BaseResponse - @POST("api/users/reissue-token") - suspend fun reissueToken( - @Body refreshToken: String - ): BaseResponse @POST("/api/users/auth/kakao") suspend fun checkKakaoEmail( diff --git a/app/src/main/java/com/kuit/ourmenu/data/service/TokenService.kt b/app/src/main/java/com/kuit/ourmenu/data/service/TokenService.kt new file mode 100644 index 00000000..9fd0c0f5 --- /dev/null +++ b/app/src/main/java/com/kuit/ourmenu/data/service/TokenService.kt @@ -0,0 +1,13 @@ +package com.kuit.ourmenu.data.service + +import com.kuit.ourmenu.data.model.auth.response.ReissueTokenResponse +import com.kuit.ourmenu.data.model.base.BaseResponse +import retrofit2.http.Body +import retrofit2.http.POST + +interface TokenService { + @POST("api/users/reissue-token") + suspend fun reissueToken( + @Body refreshToken: String + ): BaseResponse +} \ No newline at end of file From bdcc06083c9ab47e10ce37a669b9779901fae2c4 Mon Sep 17 00:00:00 2001 From: casperjr Date: Tue, 2 Sep 2025 11:09:21 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[FIX]:=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ourmenu/utils/auth/TokenAuthenticator.kt | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/kuit/ourmenu/utils/auth/TokenAuthenticator.kt b/app/src/main/java/com/kuit/ourmenu/utils/auth/TokenAuthenticator.kt index fd7c095c..c8b7d6c4 100644 --- a/app/src/main/java/com/kuit/ourmenu/utils/auth/TokenAuthenticator.kt +++ b/app/src/main/java/com/kuit/ourmenu/utils/auth/TokenAuthenticator.kt @@ -1,9 +1,9 @@ package com.kuit.ourmenu.utils.auth +import android.util.Log import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.kuit.ourmenu.BuildConfig -import com.kuit.ourmenu.data.model.auth.response.ReissueTokenResponse -import com.kuit.ourmenu.data.service.AuthService +import com.kuit.ourmenu.data.service.TokenService import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json @@ -13,7 +13,6 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okhttp3.Route -import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import javax.inject.Inject @@ -21,42 +20,46 @@ class TokenAuthenticator @Inject constructor( private val tokenManager: TokenManager, ): Authenticator { override fun authenticate(route: Route?, response: Response): Request? { - val refreshToken = runBlocking { - tokenManager.getRefreshToken().first() + // 이미 재시도했던 요청인 경우 null을 반환하여 재시도 중단 + if (response.request.header("Retry-With-New-Token") != null) { + return null } + return runBlocking { - if (refreshToken.isNullOrEmpty()){ - return@runBlocking null - } - val newToken = getNewToken(refreshToken) + try { + val refreshToken = tokenManager.getRefreshToken().first() + if (refreshToken == null) { + tokenManager.clearToken() + return@runBlocking null + } - if (newToken == null) { - tokenManager.clearToken() - } + // 토큰 재발급 요청 + val tokenService = Retrofit.Builder() + .baseUrl(BuildConfig.BASE_URL) + .client(OkHttpClient.Builder().build()) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + .create(TokenService::class.java) + val tokenResponse = tokenService.reissueToken( + refreshToken = refreshToken + ) + + // 새로운 토큰 저장 + tokenManager.saveAccessToken(tokenResponse.response?.accessToken ?: "") + tokenManager.saveRefreshToken(tokenResponse.response?.refreshToken ?: "") + Log.d("TokenAuthenticator", "토큰 재발급 성공: ${tokenResponse.response?.accessToken}") - newToken?.let { - tokenManager.saveAccessToken(it.accessToken) + // 기존 요청에 새로운 토큰으로 헤더를 추가하여 재시도 response.request.newBuilder() - .header("Authorization", "Bearer ${it.accessToken}") + .header("Authorization", "Bearer ${tokenResponse.response?.accessToken}") + .header("Retry-With-New-Token", "true") .build() + } catch (e: Exception) { + // 토큰 재발급 실패 시 토큰 클리어 + Log.d("TokenAuthenticator", "토큰 재발급 실패: ${e.message}") + tokenManager.clearToken() + null } } } - - private suspend fun getNewToken(refreshToken: String): ReissueTokenResponse? { - val loggingInterceptor = HttpLoggingInterceptor() - loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY - val okHttpClient = OkHttpClient.Builder().addInterceptor(loggingInterceptor).build() - - val retrofit = Retrofit.Builder() - .baseUrl(BuildConfig.BASE_URL) - .addConverterFactory( - Json.asConverterFactory(requireNotNull("application/json".toMediaType())) - ) - .client(okHttpClient) - .build() - val service = retrofit.create(AuthService::class.java) - return service.reissueToken(refreshToken).response - } - } \ No newline at end of file From c903781092885b5452e8b872a0e1e6b005e55b71 Mon Sep 17 00:00:00 2001 From: casperjr Date: Tue, 2 Sep 2025 12:22:59 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[REFACTOR]:=20=EC=9E=AC=EB=B0=9C=EA=B8=89?= =?UTF-8?q?=20=ED=98=B8=EC=B6=9C=20service=20=EB=B3=80=EA=B2=BD=EC=97=90?= =?UTF-8?q?=20=EB=94=B0=EB=A5=B8=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kuit/ourmenu/data/repository/AuthRepository.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/com/kuit/ourmenu/data/repository/AuthRepository.kt b/app/src/main/java/com/kuit/ourmenu/data/repository/AuthRepository.kt index 41ab85d6..f9f37190 100644 --- a/app/src/main/java/com/kuit/ourmenu/data/repository/AuthRepository.kt +++ b/app/src/main/java/com/kuit/ourmenu/data/repository/AuthRepository.kt @@ -72,11 +72,6 @@ class AuthRepository @Inject constructor( } - suspend fun reissueToken( - refreshToken: String - ) = runCatching { - authService.reissueToken(refreshToken).handleBaseResponse().getOrThrow() - } suspend fun sendEmail( email: String From bb70a3da2ca284e94d288d38d801f1ff47cb66be Mon Sep 17 00:00:00 2001 From: casperjr Date: Tue, 2 Sep 2025 12:23:38 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[FEAT]:=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EC=8B=A4=ED=8C=A8=EC=8B=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=95=84=EC=9B=83=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?Activity=EA=B0=80=20=EC=9E=AC=EC=8B=9C=EC=9E=91=20=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20event=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kuit/ourmenu/MainActivity.kt | 24 +++++++++++++++++-- .../kuit/ourmenu/utils/auth/TokenManager.kt | 6 +++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/kuit/ourmenu/MainActivity.kt b/app/src/main/java/com/kuit/ourmenu/MainActivity.kt index 40116d9e..bd3884da 100644 --- a/app/src/main/java/com/kuit/ourmenu/MainActivity.kt +++ b/app/src/main/java/com/kuit/ourmenu/MainActivity.kt @@ -1,5 +1,6 @@ package com.kuit.ourmenu +import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -12,23 +13,42 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.lifecycle.lifecycleScope +import coil3.imageLoader import com.kuit.ourmenu.ui.navigator.MainNavHost import com.kuit.ourmenu.ui.navigator.MainTab import com.kuit.ourmenu.ui.navigator.component.MainBottomBar import com.kuit.ourmenu.ui.navigator.rememberMainNavigator -import androidx.navigation.compose.rememberNavController -import coil3.imageLoader import com.kuit.ourmenu.ui.onboarding.screen.SplashScreen import com.kuit.ourmenu.ui.theme.NeutralWhite import com.kuit.ourmenu.ui.theme.OurMenuTheme +import com.kuit.ourmenu.utils.auth.TokenManager import dagger.hilt.android.AndroidEntryPoint import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.launch +import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { + @Inject + lateinit var tokenManager: TokenManager + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() + + // 로그아웃 이벤트 구독 + lifecycleScope.launch { + tokenManager.logoutEvent.collect { + // Activity 스택을 모두 클리어하고 MainActivity를 다시 시작 + val intent = Intent(this@MainActivity, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + startActivity(intent) + finish() + } + } + setContent { var showSplash by remember { mutableStateOf(true) } val navController = rememberMainNavigator() diff --git a/app/src/main/java/com/kuit/ourmenu/utils/auth/TokenManager.kt b/app/src/main/java/com/kuit/ourmenu/utils/auth/TokenManager.kt index ce62bc19..b5f9966f 100644 --- a/app/src/main/java/com/kuit/ourmenu/utils/auth/TokenManager.kt +++ b/app/src/main/java/com/kuit/ourmenu/utils/auth/TokenManager.kt @@ -6,6 +6,8 @@ import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.map import javax.inject.Inject import javax.inject.Singleton @@ -14,6 +16,9 @@ private val Context.dataStore by preferencesDataStore(name = "user_prefs") @Singleton class TokenManager @Inject constructor(@ApplicationContext private val context: Context) { + private val _logoutEvent = MutableSharedFlow() + val logoutEvent = _logoutEvent.asSharedFlow() + companion object { private val ACCESS_TOKEN = stringPreferencesKey("access_token") private val REFRESH_TOKEN = stringPreferencesKey("refresh_token") @@ -48,5 +53,6 @@ class TokenManager @Inject constructor(@ApplicationContext private val context: preferences.remove(ACCESS_TOKEN) preferences.remove(REFRESH_TOKEN) } + _logoutEvent.emit(Unit) } } \ No newline at end of file