Skip to content

Commit

Permalink
[feat/#76] interceptor 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
Mnseo committed Feb 8, 2024
1 parent 3b29ac7 commit 1bd2174
Show file tree
Hide file tree
Showing 22 changed files with 530 additions and 190 deletions.
4 changes: 4 additions & 0 deletions app/src/main/res/drawable/launch_transparent.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/transparent"/>
</selector>
3 changes: 3 additions & 0 deletions app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
<item name="android:statusBarColor">#0f1011</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowBackground">#0f1011</item>
<item name="android:windowIsTranslucent">true</item>
</style>


</resources>
89 changes: 63 additions & 26 deletions data/src/main/java/com/kusitms/data/local/AuthDataStore.kt
Original file line number Diff line number Diff line change
@@ -1,42 +1,79 @@
package com.kusitms.data.local

import android.content.Context
import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.google.gson.Gson
import com.kusitms.domain.model.login.LoginMemberProfile
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AuthDataStore {
private val KEY_AUTH_TOKEN = "KEY_AUTH_TOKEN"
private val KEY_REFRESH_TOKEN = "KEY_REFRESH_TOKEN"
private val KEY_LOGIN_MEMBER_PROFILE = "KEY_LOGIN_MEMBER_PROFILE"
class AuthDataStore @Inject constructor(private val context: Context) {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

companion object {
val AUTH_TOKEN_KEY = stringPreferencesKey("auth_token")
val REFRESH_TOKEN_KEY = stringPreferencesKey("refresh_token")
val IS_LOGIN = booleanPreferencesKey("is_login")
val KEY_LOGIN_MEMBER_PROFILE = stringPreferencesKey("login_member_profile")
}

var authToken: String
get() = DataStoreUtils.getSyncData(KEY_AUTH_TOKEN, "")
set(value) {
DataStoreUtils.saveSyncStringData(KEY_AUTH_TOKEN, value)
suspend fun saveAuthToken(token: String) {
context.dataStore.edit { preferences ->
preferences[AUTH_TOKEN_KEY] = token
}
}

var refreshToken: String
get() = DataStoreUtils.getSyncData(KEY_REFRESH_TOKEN, "")
set(value) {
DataStoreUtils.saveSyncStringData(KEY_REFRESH_TOKEN, value)
suspend fun saveLoginMemberProfile(profile: LoginMemberProfile?) {
val json = Gson().toJson(profile)
context.dataStore.edit { preferences ->
preferences[KEY_LOGIN_MEMBER_PROFILE] = json
}
}

var loginMemberProfile: LoginMemberProfile?
get() {
val json = DataStoreUtils.getSyncData(KEY_LOGIN_MEMBER_PROFILE, "")
return if (json.isNotEmpty()) {
Gson().fromJson(json, LoginMemberProfile::class.java)
} else {
null
}
val loginMemberProfile: Flow<LoginMemberProfile?> = context.dataStore.data
.map { preferences ->
val json = preferences[KEY_LOGIN_MEMBER_PROFILE] ?: return@map null
return@map Gson().fromJson(json, LoginMemberProfile::class.java)
}
set(value) {
val json = Gson().toJson(value)
DataStoreUtils.saveSyncStringData(KEY_LOGIN_MEMBER_PROFILE, json)
Log.d("AuthDataStore_loginMemberProfile", "Saving LoginMemberProfile: $json")

suspend fun updateLogin(isLogin: Boolean) {
context.dataStore.edit { preferences ->
preferences[IS_LOGIN] = isLogin
}
}

suspend fun saveRefreshToken(token: String) {
context.dataStore.edit { preferences ->
preferences[REFRESH_TOKEN_KEY] = token
}
}

suspend fun clearAllData() {
context.dataStore.edit { preferences ->
preferences.clear()
}
}

val authToken: Flow<String?> = context.dataStore.data
.map { preferences ->
preferences[AUTH_TOKEN_KEY]
}

val refreshToken: Flow<String?> = context.dataStore.data
.map { preferences ->
preferences[REFRESH_TOKEN_KEY]
}

val isLogin: Flow<Boolean?> = context.dataStore.data
.map { preferences ->
preferences[IS_LOGIN]
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.kusitms.data.remote.api

import com.kusitms.data.remote.entity.BaseResponse
import com.kusitms.data.remote.entity.response.LoginResponse
import com.kusitms.domain.model.login.TokenModel
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header

interface KusitmsTokenApi {
@GET("v1/auth/reissue")
suspend fun RefreshAccessToken() : LoginResponse
suspend fun RefreshAccessToken(@Header("RefreshToken") refreshToken: String) : LoginResponse

}
33 changes: 33 additions & 0 deletions data/src/main/java/com/kusitms/data/remote/di/AuthAuthenticator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.kusitms.data.remote.di

import com.kusitms.data.local.AuthDataStore
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import javax.inject.Inject

class AuthAuthenticator@Inject constructor(
private val authDataStore: AuthDataStore
):Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
val refreshToken = runBlocking {
authDataStore.refreshToken.first()
}

if (refreshToken == null || refreshToken == "LOGIN") {
response.close()
return null
}
return newRequestWithToken(refreshToken, response.request)
}

private fun newRequestWithToken(token: String, request: Request): Request =
request.newBuilder()
.header("Authorization", token)
.build()


}
Original file line number Diff line number Diff line change
@@ -1,51 +1,68 @@
package com.kusitms.data.remote.di


import android.util.Log
import com.kusitms.data.local.AuthDataStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.*
import timber.log.Timber
import javax.inject.Inject

class AuthTokenInterceptor @Inject constructor(
private val authDataStore: AuthDataStore,
private val tokenManager: TokenManager
) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()

val token: String? = runBlocking {
authDataStore.authToken.first()
} ?: ""

// "auth/logout" 엔드포인트 확인
if (originalRequest.url.encodedPath.endsWith("auth/logout")) {
// logout 엔드포인트의 경우 refreshToken 사용
requestBuilder.addHeader("Authorization", "${authDataStore.refreshToken}")
} else {
} else if(originalRequest.url.encodedPath.endsWith("auth/logout"))
else {
// 다른 엔드포인트의 경우 authToken 사용
requestBuilder.addHeader("Authorization", "${authDataStore.authToken}")
requestBuilder.addHeader("Authorization", "$token")
}

Log.d("Token is","${authDataStore.authToken}")
Log.d("Token is","${authDataStore.refreshToken}")
var request = requestBuilder.build()
var response = chain.proceed(request)

// 토큰 만료 감지 및 처리
if (response.code == 500 || response.code == 401) {
// 토큰 매니저 인스턴스를 가져오고 토큰 갱신을 시도
runBlocking {
launch(Dispatchers.IO) {
tokenManager.refreshAccessToken()
}.join() // 갱신 완료 대기
if (response.code == 200) {
val newAccessToken: String = response.header("Authorization", null) ?: return response
Timber.d("new Access Token = ${newAccessToken}")

CoroutineScope(Dispatchers.IO).launch {
val existedAccessToken = authDataStore.authToken.first()
if (existedAccessToken != newAccessToken) {
authDataStore.saveAuthToken(newAccessToken)
Timber.d("newAccessToken = ${newAccessToken}\nExistedAccessToken = ${existedAccessToken}")
}
}
// 갱신된 토큰으로 요청 재시도
request = chain.request().newBuilder()
.addHeader("Authorization", "${authDataStore.authToken}")
.build()
response.close() // 이전 응답 닫기
return chain.proceed(request)
} else {
Timber.e("${response.code} : ${response.request} \n ${response.message}")
}


return response
}

private fun errorResponse(request: Request): Response = Response.Builder()
.request(request)
.protocol(Protocol.HTTP_2)
.message("")
.code(401)
.body(ResponseBody.create(null, ""))
.build()
}

17 changes: 5 additions & 12 deletions data/src/main/java/com/kusitms/data/remote/di/NetworkModule.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.kusitms.data.remote.di


import android.content.Context
import com.kusitms.data.BuildConfig
import com.kusitms.data.local.AuthDataStore
import com.kusitms.data.remote.api.KusitmsApi
Expand All @@ -9,6 +10,7 @@ import com.kusitms.data.remote.util.NullOnEmptyConverterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
Expand Down Expand Up @@ -54,29 +56,20 @@ class NetworkModule {
.create(KusitmsTokenApi::class.java)
}

@Provides
@Singleton
fun provideAuthDataStore(): AuthDataStore {
return AuthDataStore()
}

@Provides
@Singleton
fun provideTokenManager(
kusitmsTokenApi: KusitmsTokenApi,
authDataStore: AuthDataStore
): TokenManager {
return TokenManager(kusitmsTokenApi, authDataStore)
fun provideAuthDataStore(@ApplicationContext context: Context): AuthDataStore {
return AuthDataStore(context)
}


@Provides
@Singleton
fun provideAuthTokenInterceptor(
authDataStore: AuthDataStore,
tokenManager: TokenManager
): AuthTokenInterceptor {
return AuthTokenInterceptor(authDataStore,tokenManager)
return AuthTokenInterceptor(authDataStore)
}


Expand Down
25 changes: 0 additions & 25 deletions data/src/main/java/com/kusitms/data/remote/di/TokenManager.kt

This file was deleted.

Loading

0 comments on commit 1bd2174

Please sign in to comment.