이전 글에서 작성 하였듯 현재 가지고 있는 accessToken으로 서비스 요청을 수행하지만 발급 된지 오래되었다면 이미 만료된 accessToken일 수 있다. 이 경우는 refreshToken을 사용해 사용자의 accessToken을 재발급 받아야한다.
Authenticator은 서버에서 인증이 필요한 경우 401상태 코드를 포함된 응답을 반환받았을 때 실패한 요청과 401상태코드를 함께 authenticate()를 호출한다.
이경우 authenticate 메서드 에서는 필요한 인증 정보를 포함하는 새로운 요청을 반환하여야 하고 인증 처리를 할 수 없다면 null을 반환하여야 한다.
@Singleton
@Provides
fun provideTokenAuthenticator(
@ApplicationContext context: Context,
authApi: AuthApi
) = Authenticator { _, response ->
val tag = "TokenAuthenticator"
val isPathRefresh =
response.request.url.toUrl().toString() == BuildConfig.BASE_URL + "auth/refresh"
if (response.code == 401 && !isPathRefresh) {
try {
val refreshToken = EncryptedPrefs.getString(PrefsKey.REFRESH_TOKEN_KEY)
val tokenRefreshRequest = TokenRefreshRequest(refreshToken)
val resp = authApi.tokenRefresh(tokenRefreshRequest).execute()
EncryptedPrefs.clearPrefs()
if (!resp.isSuccessful) {
IntroActivity.startActivity(context)
throw TokenRefreshFailedException("토큰 갱신 실패")
}
val token = resp.body() ?: throw TokenEmptyException("받아온 토큰 값이 null임")
EncryptedPrefs.putString(PrefsKey.ACCESS_TOKEN_KEY, token.accessToken)
EncryptedPrefs.putString(PrefsKey.REFRESH_TOKEN_KEY, token.refreshToken)
response.request.newBuilder().apply {
removeHeader("Authorization")
addHeader("Authorization", "Bearer ${token.accessToken}")
}.build()
} catch (e: Exception) {
Log.e(tag, e.message.toString(), e)
}
}
null
}
이것 역시 hilt로 주입해주었다. 구현된 코드의 동작 흐름을 따라가보자
위의 과정으로 토큰 갱신과정이 문제없이 수행될 것이라 기대하였지만 새로운 문제가 발생하였다.
fun provideBabaClient(
authorizationInterceptor: Interceptor,
tokenAuthenticator: Authenticator
): OkHttpClient {
val builder = OkHttpClient.Builder()
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.apply {
addInterceptor(loggingInterceptor)
addInterceptor(authorizationInterceptor)
}
builder.authenticator(tokenAuthenticator)
return builder.build()
}
위와 같이 OkhttpClient를 생성하고 모든 Retrofit객체에서 사용하게 될 경우 문제가 발생한다.
TokenAuthenticator에서 AuthApi를 주입받고 있는데 해당 API를 구현할 때 주입받는 Retrofit 객체에서 다시 OkhttpClient를 주입받고 있다.
정리하자면
TokenAuthenticator -> AuthApi -> Retrofit -> OkHttpClient -> TokenAuthenticator 의 순서로 구현을 기다리게 되는 무한 루프에 빠지게 된다.
이를 해결하기 위해 AuthApi에서 사용할 Retrofit에서는 Authenticator을 가지지 않는 별도의 OkHttpClient를 구현해 줄 필요가 있었다.
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BabaClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthClient
@BabaClient
@Singleton
@Provides
fun provideBabaClient(
authorizationInterceptor: Interceptor,
tokenAuthenticator: Authenticator
): OkHttpClient {
val builder = OkHttpClient.Builder()
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.apply {
addInterceptor(loggingInterceptor)
addInterceptor(authorizationInterceptor)
}
builder.authenticator(tokenAuthenticator)
return builder.build()
}
@AuthClient
@Singleton
@Provides
fun provideAuthClient(
authorizationInterceptor: Interceptor
): OkHttpClient {
val builder = OkHttpClient.Builder()
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.apply {
addInterceptor(loggingInterceptor)
addInterceptor(authorizationInterceptor)
}
return builder.build()
}
힐트에서 같은 타입의 객체를 구분하여 주입받기 원할때는 Qualifier를 통해 새로운 어노테이션을 생성하고 그것을 붙여주어 구분할 수 있다.
fun provideAuthRetrofit(
@NetworkModule.AuthClient
okHttpClient: OkHttpClient,
gsonConverterFactory: GsonConverterFactory
): Retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(gsonConverterFactory)
.build()
필요한 Client타입을 어노테이션으로 구분하여 적절하게 주입해주어 문제를 해결하였다.
잘 보고 갑니다!