Skip to content

Commit

Permalink
Refresh 토큰을 통한 JWT 토큰 재발급 로직 추가 (#220)
Browse files Browse the repository at this point in the history
* Refresh 토큰을 통한 JWT 토큰 재발급 로직 추가

* style check success

* style check success 및 Exception Handling 추가

토큰 재발급 로직 중 문제가 발생할 경우 RefreshTokenExpiredException 을 던져 로그아웃 되도록

* run build success
  • Loading branch information
easyhooon authored Feb 13, 2024
1 parent 16efcce commit 6d7164b
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 14 deletions.
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>
}

0 comments on commit 6d7164b

Please sign in to comment.