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

Refresh 토큰을 통한 JWT 토큰 재발급 로직 추가 #220

Merged
merged 4 commits into from
Feb 13, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Designed and developed by Wedemy 2023.
*
* Licensed under the MIT.
* Please see full license: https://github.com/Wedemy/eggeum-android/blob/main/LICENSE
*/

package us.wedemy.eggeum.android.data.datasource.token

import us.wedemy.eggeum.android.data.model.token.TokenRequest
import us.wedemy.eggeum.android.data.model.token.TokenResponse

public interface TokenDataSource {
public suspend fun getRefreshToken(tokenRequest: TokenRequest): TokenResponse?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Designed and developed by Wedemy 2023.
*
* Licensed under the MIT.
* Please see full license: https://github.com/Wedemy/eggeum-android/blob/main/LICENSE
*/

package us.wedemy.eggeum.android.data.datasource.token

import javax.inject.Inject
import us.wedemy.eggeum.android.data.model.token.TokenRequest
import us.wedemy.eggeum.android.data.model.token.TokenResponse
import us.wedemy.eggeum.android.data.service.TokenService
import us.wedemy.eggeum.android.data.util.safeRequest

public class TokenDataSourceImpl @Inject constructor(
private val service: TokenService,
) : TokenDataSource {

override suspend fun getRefreshToken(tokenRequest: TokenRequest): TokenResponse? {
return safeRequest {
service.getRefreshToken(tokenRequest)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import us.wedemy.eggeum.android.data.datasource.place.remote.PlaceRemoteDataSour
import us.wedemy.eggeum.android.data.datasource.place.remote.PlaceRemoteDataSourceImpl
import us.wedemy.eggeum.android.data.datasource.report.ReportDataSource
import us.wedemy.eggeum.android.data.datasource.report.ReportDataSourceImpl
import us.wedemy.eggeum.android.data.datasource.token.TokenDataSource
import us.wedemy.eggeum.android.data.datasource.token.TokenDataSourceImpl
import us.wedemy.eggeum.android.data.datasource.user.UserDataSource
import us.wedemy.eggeum.android.data.datasource.user.UserDataSourceImpl

Expand Down Expand Up @@ -63,4 +65,8 @@ internal abstract class DataSourceModule {
@Binds
@Singleton
abstract fun bindFileDataSource(fileDataSourceImpl: FileDataSourceImpl): FileDataSource

@Binds
@Singleton
abstract fun bindTokenDataSource(tokenDataSourceImpl: TokenDataSourceImpl): TokenDataSource
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import timber.log.Timber
import us.wedemy.eggeum.android.data.BuildConfig
import us.wedemy.eggeum.android.data.datasource.token.TokenDataSource
import us.wedemy.eggeum.android.data.datastore.TokenDataStoreProvider
import us.wedemy.eggeum.android.data.service.TokenAuthenticator
import us.wedemy.eggeum.android.data.service.TokenInterceptor
Expand Down Expand Up @@ -129,8 +130,9 @@ internal object NetworkModule {
@Provides
internal fun provideTokenAuthenticator(
dataStoreProvider: TokenDataStoreProvider,
tokenDataSource: TokenDataSource,
): TokenAuthenticator {
return TokenAuthenticator(dataStoreProvider)
return TokenAuthenticator(dataStoreProvider, tokenDataSource)
}

@Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import us.wedemy.eggeum.android.data.service.LoginService
import us.wedemy.eggeum.android.data.service.NoticeService
import us.wedemy.eggeum.android.data.service.PlaceService
import us.wedemy.eggeum.android.data.service.ReportService
import us.wedemy.eggeum.android.data.service.TokenService
import us.wedemy.eggeum.android.data.service.UserService

@Module
Expand Down Expand Up @@ -78,4 +79,13 @@ internal object ServiceModule {
): FileService {
return retrofit.create(FileService::class.java)
}

@Singleton
@Provides
internal fun provideTokenService(
@Named("RetrofitAuthHttpClient")
retrofit: Retrofit,
): TokenService {
return retrofit.create(TokenService::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Designed and developed by Wedemy 2023.
*
* Licensed under the MIT.
* Please see full license: https://github.com/Wedemy/eggeum-android/blob/main/LICENSE
*/

package us.wedemy.eggeum.android.data.model.token

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
public data class TokenRequest(
@SerialName("refreshToken")
val refreshToken: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Designed and developed by Wedemy 2023.
*
* Licensed under the MIT.
* Please see full license: https://github.com/Wedemy/eggeum-android/blob/main/LICENSE
*/

package us.wedemy.eggeum.android.data.model.token

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
public data class TokenResponse(
@SerialName("accessToken")
val accessToken: String,

@SerialName("expiresIn")
val expiresIn: Long,

@SerialName("refreshToken")
val refreshToken: String,

@SerialName("refreshExpiresIn")
val refreshExpiresIn: Long,

@SerialName("userRoles")
val userRoles: List<String>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,32 @@ import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import timber.log.Timber
import us.wedemy.eggeum.android.data.datasource.token.TokenDataSource
import us.wedemy.eggeum.android.data.datastore.TokenDataStoreProvider
import us.wedemy.eggeum.android.data.model.token.TokenRequest
import us.wedemy.eggeum.android.domain.util.RefreshTokenExpiredException

@Suppress("TooGenericExceptionCaught")
public class TokenAuthenticator @Inject constructor(
private val dataStoreProvider: TokenDataStoreProvider,
private val tokenDataSource: TokenDataSource,
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
Timber.d("authenticate 호출")
return runBlocking {
val currentAccessToken = dataStoreProvider.getAccessToken()

// 현재 액세스 토큰이 요청 헤더의 토큰과 다르면 이미 갱신된 것으로 간주
if (response.request.header("Authorization") != "Bearer $currentAccessToken") {
Timber.d("RefreshToken is Expired")
// 로그인 토큰 제거(로그아웃)
// dataStoreProvider.clear()
throw RefreshTokenExpiredException
}

try {
val newAccessToken = runBlocking { dataStoreProvider.getRefreshToken() }
newRequestWithAccessToken(response.request, newAccessToken)
val refreshToken = dataStoreProvider.getRefreshToken()
val tokenResponse = tokenDataSource.getRefreshToken(TokenRequest(refreshToken))
if (tokenResponse != null) {
dataStoreProvider.setAccessToken(tokenResponse.accessToken)
dataStoreProvider.setRefreshToken(tokenResponse.refreshToken)
newRequestWithAccessToken(response.request, tokenResponse.accessToken)
} else {
null
}
} catch (e: Exception) {
Timber.e("TokenAuthenticator Error :: ${e.message}")
null
throw RefreshTokenExpiredException
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Designed and developed by Wedemy 2023.
*
* Licensed under the MIT.
* Please see full license: https://github.com/Wedemy/eggeum-android/blob/main/LICENSE
*/

package us.wedemy.eggeum.android.data.service

import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST
import us.wedemy.eggeum.android.data.model.token.TokenRequest
import us.wedemy.eggeum.android.data.model.token.TokenResponse

public interface TokenService {

@POST("app/token/refresh")
public suspend fun getRefreshToken(
@Body tokenRequest: TokenRequest,
): Response<TokenResponse>
}
Loading