Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT/#96] 토큰 재발급 / 서버통신 구현 #102

Merged
merged 6 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ dependencies {
implementation(libs.retrofit2.kotlinx.serialization.converter)
implementation(libs.timber)
implementation(libs.ossLicense)
implementation(libs.process.phoenix)

debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
Expand Down
89 changes: 89 additions & 0 deletions app/src/main/java/com/terning/point/di/AuthInterceptor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.terning.point.di

import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import com.jakewharton.processphoenix.ProcessPhoenix
import com.terning.core.extension.stringToast
import com.terning.data.local.TerningDataStore
import com.terning.domain.repository.TokenReissueRepository
import com.terning.feature.main.MainActivity
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import timber.log.Timber
import javax.inject.Inject

class AuthInterceptor @Inject constructor(
private val tokenReissueRepository: TokenReissueRepository,
private val terningDataStore: TerningDataStore,
@ApplicationContext private val context: Context
) : Interceptor {

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

Timber.d("GET ACCESS TOKEN : ${terningDataStore.accessToken}")

val authRequest = if (terningDataStore.accessToken.isNotBlank()) {
originalRequest.newBuilder().newAuthBuilder().build()
} else {
originalRequest
}

val response = chain.proceed(authRequest)

when (response.code) {
CODE_TOKEN_EXPIRED -> {
try {
runBlocking {
tokenReissueRepository.postReissueToken(
terningDataStore.refreshToken
)
}.onSuccess { data ->
terningDataStore.apply {
refreshToken = data.refreshToken
}

response.close()

val newRequest =
authRequest.newBuilder().removeHeader(AUTHORIZATION).newAuthBuilder()
.build()

return chain.proceed(newRequest)
}
} catch (t: Throwable) {
Timber.d(t.message)
}

terningDataStore.clearInfo()

Handler(Looper.getMainLooper()).post {
context.stringToast(TOKEN_EXPIRED_ERROR)
Handler(Looper.getMainLooper()).post {
ProcessPhoenix.triggerRebirth(
context,
Intent(context, MainActivity::class.java)
)
}
}
}
}
return response
}

private fun Request.Builder.newAuthBuilder() =
this.addHeader(AUTHORIZATION, "$BEARER ${terningDataStore.accessToken}")

companion object {
private const val CODE_TOKEN_EXPIRED = 401
private const val TOKEN_EXPIRED_ERROR = "토큰이 만료되었어요\n다시 로그인 해주세요"
private const val BEARER = "Bearer"
private const val AUTHORIZATION = "Authorization"
}

}
6 changes: 6 additions & 0 deletions app/src/main/java/com/terning/point/di/DataSourceModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.terning.point.di

import com.terning.data.datasource.AuthDataSource
import com.terning.data.datasource.SearchDataSource
import com.terning.data.datasource.TokenReissueDataSource
import com.terning.data.datasourceimpl.AuthDataSourceImpl
import com.terning.data.datasourceimpl.SearchDataSourceImpl
import com.terning.data.datasourceimpl.TokenReissueDataSourceImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All @@ -22,4 +24,8 @@ abstract class DataSourceModule {
@Singleton
abstract fun bindSearchViewsDataSource(searchViewsDataSourceImpl: SearchDataSourceImpl):
SearchDataSource

@Binds
@Singleton
abstract fun bindTokenReissueDataSource(tokenReissueDataSourceImpl: TokenReissueDataSourceImpl): TokenReissueDataSource
}
6 changes: 6 additions & 0 deletions app/src/main/java/com/terning/point/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package com.terning.point.di

import com.terning.data.repositoryimpl.AuthRepositoryImpl
import com.terning.data.repositoryimpl.SearchViewsRepositoryImpl
import com.terning.data.repositoryimpl.TokenReissueRepositoryImpl
import com.terning.data.repositoryimpl.TokenRepositoryImpl
import com.terning.domain.repository.AuthRepository
import com.terning.domain.repository.SearchRepository
import com.terning.domain.repository.TokenReissueRepository
import com.terning.domain.repository.TokenRepository
import dagger.Binds
import dagger.Module
Expand All @@ -27,4 +29,8 @@ abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindSearchViewsRepository(searchViewsRepositoryImpl: SearchViewsRepositoryImpl): SearchRepository

@Binds
@Singleton
abstract fun bindTokenReissueRepository(tokenReissueRepositoryImpl: TokenReissueRepositoryImpl): TokenReissueRepository
}
38 changes: 34 additions & 4 deletions app/src/main/java/com/terning/point/di/RetrofitModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.terning.core.extension.isJsonArray
import com.terning.core.extension.isJsonObject
import com.terning.point.BuildConfig.BASE_URL
import com.terning.point.di.qualifier.JWT
import com.terning.point.di.qualifier.REISSUE
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand Down Expand Up @@ -57,8 +58,25 @@ object RetrofitModule {

@Provides
@Singleton
fun provideOkHttpClient(
loggingInterceptor: Interceptor
@JWT
fun provideAuthInterceptor(authInterceptor: AuthInterceptor): Interceptor = authInterceptor

@Provides
@Singleton
@JWT
fun provideJWTOkHttpClient(
loggingInterceptor: Interceptor,
@JWT authInterceptor: Interceptor,
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.build()

@Provides
@Singleton
@REISSUE
fun provideReissueOkHttpClient(
loggingInterceptor: Interceptor,
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
Expand All @@ -67,12 +85,24 @@ object RetrofitModule {
@Singleton
@JWT
fun provideJWTRetrofit(
client: OkHttpClient,
factory: Converter.Factory
@JWT client: OkHttpClient,
factory: Converter.Factory,
): Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(factory)
.build()

@Provides
@Singleton
@REISSUE
fun provideReissueRetrofit(
@REISSUE client: OkHttpClient,
factory: Converter.Factory,
): Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(factory)
.build()

}
7 changes: 7 additions & 0 deletions app/src/main/java/com/terning/point/di/ServiceModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package com.terning.point.di

import com.terning.data.service.AuthService
import com.terning.data.service.SearchService
import com.terning.data.service.TokenReissueService
import com.terning.point.di.qualifier.JWT
import com.terning.point.di.qualifier.REISSUE
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand All @@ -23,4 +25,9 @@ object ServiceModule {
@Singleton
fun provideSearchService(@JWT retrofit: Retrofit): SearchService =
retrofit.create(SearchService::class.java)

@Provides
@Singleton
fun provideTokenReissueService(@REISSUE retrofit: Retrofit): TokenReissueService =
retrofit.create(TokenReissueService::class.java)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ import javax.inject.Qualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class JWT
annotation class JWT

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class REISSUE
4 changes: 4 additions & 0 deletions core/src/main/java/com/terning/core/extension/ContextExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ fun Context.toast(@StringRes message: Int) {
Toast.makeText(this, getString(message), Toast.LENGTH_SHORT).show()
}

fun Context.stringToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}

fun Context.longToast(@StringRes message: Int) {
Toast.makeText(this, getString(message), Toast.LENGTH_SHORT).show()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.terning.data.datasource

import com.terning.data.dto.BaseResponse
import com.terning.data.dto.response.TokenReissueResponseDto
import com.terning.domain.entity.response.TokenReissueResponseModel

interface TokenReissueDataSource {
suspend fun postReissueToken(
authorization: String,
): BaseResponse<TokenReissueResponseDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.terning.data.datasourceimpl

import com.terning.data.datasource.TokenReissueDataSource
import com.terning.data.dto.BaseResponse
import com.terning.data.dto.response.TokenReissueResponseDto
import com.terning.data.service.TokenReissueService
import javax.inject.Inject

class TokenReissueDataSourceImpl @Inject constructor(
private val tokenReissueService: TokenReissueService
) : TokenReissueDataSource {
override suspend fun postReissueToken(
authorization: String
): BaseResponse<TokenReissueResponseDto> =
tokenReissueService.postReissueToken(authorization)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.terning.data.dto.response

import com.terning.domain.entity.response.TokenReissueResponseModel
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class TokenReissueResponseDto(
@SerialName("refreshToken")
val refreshToken: String
) {
fun toTokenReissueResponseModel() = TokenReissueResponseModel(refreshToken = refreshToken)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.terning.data.repositoryimpl

import com.terning.data.datasource.TokenReissueDataSource
import com.terning.domain.entity.response.TokenReissueResponseModel
import com.terning.domain.repository.TokenReissueRepository
import javax.inject.Inject

class TokenReissueRepositoryImpl @Inject constructor(
private val tokenReissueDataSource: TokenReissueDataSource
) : TokenReissueRepository {
override suspend fun postReissueToken(
authorization: String
): Result<TokenReissueResponseModel> =
runCatching {
tokenReissueDataSource.postReissueToken(
authorization = authorization
).result.toTokenReissueResponseModel()
}
}
13 changes: 13 additions & 0 deletions data/src/main/java/com/terning/data/service/TokenReissueService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.terning.data.service

import com.terning.data.dto.BaseResponse
import com.terning.data.dto.response.TokenReissueResponseDto
import retrofit2.http.Header
import retrofit2.http.POST

interface TokenReissueService {
@POST("/api/v1/auth/token-reissue")
suspend fun postReissueToken(
@Header("Authorization") authorization: String,
): BaseResponse<TokenReissueResponseDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.terning.domain.entity.response

data class TokenReissueResponseModel (
val refreshToken : String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.terning.domain.repository

import com.terning.domain.entity.response.TokenReissueResponseModel

interface TokenReissueRepository {
suspend fun postReissueToken(
authorization: String,
): Result<TokenReissueResponseModel>
}
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ lifecycleRuntimeComposeAndroid = "2.8.2"
## Kakao
kakaoVersion = "2.20.1"

## ProcessPhoenix
processPhoenix = "2.0.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" }
Expand Down Expand Up @@ -159,6 +162,8 @@ lottie = {group = "com.airbnb.android", name = "lottie", version.ref = "lottieVe

kakao-user = {group = "com.kakao.sdk", name = "v2-user", version.ref = "kakaoVersion"}

process-phoenix = {group = "com.jakewharton", name = "process-phoenix", version.ref = "processPhoenix"}

[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
Expand Down
Loading