diff --git a/app/src/main/java/com/kuit/ourmenu/MainActivity.kt b/app/src/main/java/com/kuit/ourmenu/MainActivity.kt index 40116d9..bd3884d 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/data/repository/AuthRepository.kt b/app/src/main/java/com/kuit/ourmenu/data/repository/AuthRepository.kt index 41ab85d..f9f3719 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 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 798ed8b..4fbd851 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 0000000..9fd0c0f --- /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 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 fd7c095..c8b7d6c 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 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 ce62bc1..b5f9966 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