Skip to content

Commit

Permalink
Merge pull request #158 from EAT-SSU/refactor/token_reissue
Browse files Browse the repository at this point in the history
[HotFix&Refactor/#155 #75] ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์ด์Šˆ ํ•ด๊ฒฐ
  • Loading branch information
HI-JIN2 authored Mar 28, 2024
2 parents bd7af55 + 21b7aa9 commit bb5fcbb
Show file tree
Hide file tree
Showing 16 changed files with 300 additions and 226 deletions.
1 change: 0 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ android {
minSdk 23
compileSdkVersion 34
targetSdk 34
versionCode 9
versionName "1.1.7"
versionCode 10
versionName "1.1.8"


buildConfigField "String", "BASE_URL", properties["BASE_URL"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.eatssu.android.data.usecase

import com.eatssu.android.base.BaseResponse
import com.eatssu.android.data.dto.response.MyReviewResponse
import com.eatssu.android.data.repository.UserRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class GetMyReviewsUseCase @Inject constructor(
private val userRepository: UserRepository,
) {
suspend operator fun invoke(): Flow<BaseResponse<MyReviewResponse>> =
userRepository.getUserReviews()
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package com.eatssu.android.data.usecase

import com.eatssu.android.App
import com.eatssu.android.base.BaseResponse
import com.eatssu.android.data.repository.UserRepository
import com.eatssu.android.util.MySharedPreferences
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class SignOutUseCase @Inject constructor(
private val userRepository: UserRepository,
) {
suspend operator fun invoke(): Flow<BaseResponse<Boolean>> {
MySharedPreferences.clearUser(App.appContext)

return userRepository.signOut()
}
}
suspend operator fun invoke(): Flow<BaseResponse<Boolean>> =
userRepository.signOut()
}
95 changes: 0 additions & 95 deletions app/src/main/java/com/eatssu/android/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,99 +71,4 @@ object NetworkModule {
fun provideUserService(retrofit: Retrofit): UserService {
return retrofit.create(UserService::class.java)
}

/*
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MultiClient
@Provides
@Singleton
fun provideGsonConverterFactory(): GsonConverterFactory {
return GsonConverterFactory.create()
}
@Provides
@Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
}
@Provides
@Singleton
fun provideAppInterceptor(): AppInterceptor {
return AppInterceptor(App.appContext)
}
@Singleton
@Provides
fun provideClient(//์ผ๋ฐ˜
// authorizationInterceptor: Interceptor,
// tokenAuthenticator: Authenticator,
// cacheInterceptor: CacheInterceptor
appInterceptor: AppInterceptor
): OkHttpClient {
val builder = OkHttpClient.Builder()
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.apply {
addInterceptor(loggingInterceptor)
addInterceptor(appInterceptor)
// addInterceptor(cacheInterceptor)
// addInterceptor(authorizationInterceptor)
}
// builder.authenticator(tokenAuthenticator)
return builder.build()
}
@AuthClient
@Singleton
@Provides
fun provideAuthClient( //์ด๊ฒŒ ํ† ํฐ ์—†๋Š”๊ฑฐ
// authorizationInterceptor: Interceptor,
appInterceptor: AppInterceptor,
// cacheInterceptor: CacheInterceptor
): OkHttpClient {
val builder = OkHttpClient.Builder()
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.apply {
addInterceptor(loggingInterceptor)
// addInterceptor(appInterceptor)
// addInterceptor(cacheInterceptor)
// addInterceptor(authorizationInterceptor)
}
return builder.build()
}
@MultiClient
@Singleton
@Provides
fun provideMultiClient(
// authorizationInterceptor: Interceptor,
multiAppInterceptor: MultiAppInterceptor,
// cacheInterceptor: CacheInterceptor,
): OkHttpClient {
val builder = OkHttpClient.Builder()
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.apply {
addInterceptor(loggingInterceptor)
addInterceptor(multiAppInterceptor)
// addInterceptor(cacheInterceptor)
// addInterceptor(authorizationInterceptor)
}
return builder.build()
}
*/
}
130 changes: 90 additions & 40 deletions app/src/main/java/com/eatssu/android/di/network/TokenInterceptor.kt
Original file line number Diff line number Diff line change
@@ -1,96 +1,146 @@
package com.eatssu.android.di.network


import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import com.eatssu.android.App
import com.eatssu.android.BuildConfig.BASE_URL
import com.eatssu.android.base.BaseResponse
import com.eatssu.android.data.dto.response.TokenResponse
import com.eatssu.android.data.usecase.GetAccessTokenUseCase
import com.eatssu.android.data.usecase.GetRefreshTokenUseCase
import com.eatssu.android.data.usecase.LogoutUseCase
import com.eatssu.android.data.usecase.ReissueTokenUseCase
import com.eatssu.android.data.usecase.SetAccessTokenUseCase
import com.eatssu.android.data.usecase.SetRefreshTokenUseCase
import com.eatssu.android.ui.login.LoginActivity
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import java.lang.reflect.Type
import javax.inject.Inject

class TokenInterceptor @Inject constructor(
private val getAccessTokenUseCase: GetAccessTokenUseCase,
private val getRefreshTokenUseCase: GetRefreshTokenUseCase,
private val setAccessTokenUseCase: SetAccessTokenUseCase,
private val setRefreshTokenUseCase: SetRefreshTokenUseCase,
private val logoutUseCase: LogoutUseCase,
private val reissueTokenUseCase: ReissueTokenUseCase,
) : Interceptor {

companion object {
val EXCEPT_LIST = listOf(
"/login/google", "/reissue", "/users",
"/",

"/oauths/reissue/token",
"/oauths/kakao",

"/oauths/**", "/users/**",
"/menus/**", "/meals/**", "/restaurants/**", "/reviews/**",

"/inquiries/{userInquiriesId}",
"/inquiries/list",

"/admin/login"
)

const val TAG = "TokenInterceptor"

private const val CODE_TOKEN_EXPIRED = 401
private const val HEADER_AUTHORIZATION = "Authorization"
private const val HEADER_ACCESS_TOKEN = "X-ACCESS-AUTH"
private const val HEADER_REFRESH_TOKEN = "X-REFRESH-AUTH"
}

private lateinit var newAccessToken: String
private lateinit var newRefreshToken: String

override fun intercept(chain: Interceptor.Chain): Response {
val token = runBlocking { getAccessTokenUseCase() }
val accessToken = runBlocking { getAccessTokenUseCase() }
val refreshToken = runBlocking { getRefreshTokenUseCase() }

val originalRequest = chain.request()
val request = chain.request().newBuilder().apply {
if (EXCEPT_LIST.none { originalRequest.url.encodedPath.endsWith(it) }) {
addHeader("accept", "application/hal+json")
addHeader("Content-Type", "application/json")
addHeader("Authorization", "Bearer $token")
addHeader(HEADER_AUTHORIZATION, "Bearer $accessToken")
}
}.build()

val response = chain.proceed(request)

if (response.code == 401) {
// if (response.code == 401 && !response.request.url.toString().contains("reissue")) {
// refresh token
Log.d(TAG, "ํ† ํฐ ํ‰คํ‰ค")
response.close()

Log.d(TAG, "ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์‹œ๋„")
try {
val refreshToken = runBlocking { getRefreshTokenUseCase() }
val newAccessToken = runBlocking { reissueTokenUseCase(refreshToken) }

// ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์€ ํ† ํฐ์œผ๋กœ ์ƒˆ๋กœ์šด ์š”์ฒญ ์ƒ์„ฑ
val newRequest = request.newBuilder()
.removeHeader("Authorization")
.addHeader("Authorization", "Bearer $newAccessToken")
val refreshTokenRequest = originalRequest.newBuilder()
.post("".toRequestBody())
.url("$BASE_URL/oauths/reissue/token")
.addHeader(HEADER_AUTHORIZATION, "Bearer $refreshToken")
.build()

chain.proceed(newRequest)
Log.d(TAG, "์žฌ๋ฐœ๊ธ‰ ์ค‘")

val refreshTokenResponse = chain.proceed(refreshTokenRequest)
Log.d(TAG, "refreshTokenResponse : $refreshTokenResponse")

if (refreshTokenResponse.isSuccessful) {
Log.d(TAG, "์žฌ๋ฐœ๊ธ‰ ์„ฑ๊ณต")

val responseToken = parseRefreshTokenResponse(refreshTokenResponse)

responseToken?.result?.let {
runBlocking {
setAccessTokenUseCase(it.accessToken)
setRefreshTokenUseCase(it.refreshToken)

newAccessToken = it.accessToken
}
}

refreshTokenResponse.close()
val newRequest = originalRequest.newAuthBuilder().build()
return chain.proceed(newRequest)
}

} catch (e: Exception) {
Log.d(TAG, "ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์‹คํŒจ" + response)
// e.printStackTrace() //์ด๊ฑฐ ๋นผ๋‹ˆ๊นŒ ๊ฐ•์ œ์ข…๋ฃŒ ์•ˆ๋œ๋‹ค

// ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์–ด ์žฌ๋ฐœ๊ธ‰์— ์‹คํŒจํ–ˆ์„ ๋•Œ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ
// if (response.code == 403) {
runBlocking { logoutUseCase() }

Handler(Looper.getMainLooper()).post {
val context = App.appContext
Toast.makeText(context, "ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์–ด ๋กœ๊ทธ์•„์›ƒ ๋ฉ๋‹ˆ๋‹ค.", Toast.LENGTH_SHORT).show()
val intent = Intent(context, LoginActivity::class.java) // ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent)
// }
runBlocking { logoutUseCase() }
Log.d(TAG, "์žฌ๋ฐœ๊ธ‰ ์‹คํŒจ $e")

Handler(Looper.getMainLooper()).post {
val context = App.appContext
Toast.makeText(context, "ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์–ด ๋กœ๊ทธ์•„์›ƒ ๋ฉ๋‹ˆ๋‹ค.", Toast.LENGTH_SHORT).show()
val intent = Intent(context, LoginActivity::class.java) // ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent)
}
}
}

if (response.code == 404) {
runBlocking { logoutUseCase() }
Log.e(TAG, "๋‹ค๋ฅธ ์œ ์ €!")

Handler(Looper.getMainLooper()).post {
val context = App.appContext
Toast.makeText(context, "ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์–ด ๋กœ๊ทธ์•„์›ƒ ๋ฉ๋‹ˆ๋‹ค.", Toast.LENGTH_SHORT).show()
val intent = Intent(context, LoginActivity::class.java) // ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent)
}
}

return response
}

private fun Request.newAuthBuilder() =
this.newBuilder().addHeader(HEADER_AUTHORIZATION, "Bearer $newAccessToken")

private fun parseRefreshTokenResponse(response: Response): BaseResponse<TokenResponse>? {
return try {
val gson = Gson()
val responseType: Type = object : TypeToken<BaseResponse<TokenResponse>>() {}.type
gson.fromJson(response.body?.string(), responseType)
} catch (e: Exception) {
null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ class IntroViewModel @Inject constructor(
private val _uiState: MutableStateFlow<IntroState> = MutableStateFlow(IntroState())
val uiState: StateFlow<IntroState> = _uiState.asStateFlow()

init {
autoLogin()
}

fun autoLogin() {
viewModelScope.launch {
if (getAccessTokenUseCase().isNotEmpty()) {
if (getAccessTokenUseCase().isEmpty()) {
_uiState.update { it.copy(isAutoLogined = false) }
} else {
_uiState.update { it.copy(isAutoLogined = true) }
Expand Down
Loading

0 comments on commit bb5fcbb

Please sign in to comment.