본문 바로가기
안드로이드

okhttp3를 이용하여 재인증하기(refresh token)

by 아카이sun 2019. 10. 3.

지난시간에는 retrofit2와 okhttp3의 해더를 이용해 인증하는 방법에 대해서 알아보았습니다.

https://akaisun.tistory.com/72

 

retrofit2를 사용하여 Authorization 인증하기

앱에서 서버와 통신할 때 사용자의 인증이 필요한경우 해더에 Authorization를 넣어 통신한다. Authorization에도 BearerToken, ApiKey, OAuth2.0등 여러가지 종류가 있다. 그럼 어떻게 해더에 Authorization를 넣..

akaisun.tistory.com

 

 

앱에서 서버로 인증할때는 OAuth 2.0방법을 가장많이 사용하는데 이에 대한 설명은 아래의 링크를 참조해주세요.

https://oauth.net/2/

 

OAuth 2.0 — OAuth

OAuth 2.0 OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 supersedes the work done on the original OAuth protocol created in 2006. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for we

oauth.net

OAuth 2.0에서는  JWT(Json Web Token)를 이용합니다.

이는 인증정보를 암호화하여 url형식으로 전달해 주는 토큰이라고 합니다.

Token이라는 것은 만료시간이 있으며 만료된 Token은 더이상 사용하지 못합니다.

만약 만료된 Token으로 서버와 통신한다면 401 error를 만나게 됩니다.

따라서 만료된 access token은 refresh token으로 새로운 access token을 갱신받아 사용해야만 합니다.

 

그럼 안드로이드에서는 토큰 갱신을 어떻게 구현할까요?

바로 okhttp3를 통해 쉽게 구현할 수 있습니다.

val builder =  OkHttpClient.Builder()
	.connectTimeout(TIMEOUT_LIMIT, TimeUnit.SECONDS)
	.readTimeout(TIMEOUT_LIMIT, TimeUnit.SECONDS)
	.writeTimeout(TIMEOUT_LIMIT, TimeUnit.SECONDS)
	.authenticator(authenticator)

빌드에서 authenticator를 추가만 하면 됩니다.

그럼 authenticator는 어떻게 구현할까요? 아래의 예제가 있습니다.

class TokenAuthenticator constructor(
    val context: Context,
    private val refreshToken: String,
) : Authenticator {

	companion object {
        private val TAG = TokenAuthenticator::class.java.simpleName
    }

    override fun authenticate(route: Route?, response: Response): Request? {
   
        if (response.code == 401) {

            val refreshToken = CommonHelper.getRefreshToken(sharedPref)
            val getNewDeviceToken = GlobalScope.async(Dispatchers.Default) {
                getNewDeviceToken(refreshToken)
            }

            val token = runBlocking {
                getNewDeviceToken.await()
            }
            if(token != null) {
                return getRequest(response, token)
            } 
        }
        return null
    }

    private suspend inline fun getNewDeviceToken(token: String): String? {
        return GlobalScope.async(Dispatchers.Default) {
            callApiNewDeviceToken(token)
        }.await()
    }


    private suspend inline fun callApiNewDeviceToken(token: String) : String? = suspendCoroutine { continuation ->
        createWebService<Api>()
            .refreshToken(RefreshToken(token))
            .with(rx)
            .response(object : ApiCallback<Token>{
                override fun success(data: Token?) {
                    if(data != null) {
                        CommonHelper.saveTokenInfo(sharedPref, data)
                        continuation.resume(data.accessToken)
                    } else {
                        continuation.resume(null)
                    }
                }

                override fun error(statusCode: Int, message: String?) {
                    continuation.resume(null)
                }
            })

        return@suspendCoroutine
    }

    private val okHttp =  OkHttpClient.Builder()
        .connectTimeout(TIMEOUT_LIMIT, TimeUnit.SECONDS)
        .readTimeout(TIMEOUT_LIMIT, TimeUnit.SECONDS)
        .writeTimeout(TIMEOUT_LIMIT, TimeUnit.SECONDS)
        .addInterceptor(HttpLoggingInterceptor().apply {
            level = if (BuildConfig.DEBUG) {
                HttpLoggingInterceptor.Level.BODY
            } else {
                HttpLoggingInterceptor.Level.NONE
            }
        })
        .build()

    private inline fun <reified T> createWebService(): T {
        val retrofit = Retrofit.Builder()
            .baseUrl(BuildConfig.SERVER_URL)
            .client(okHttp)
            .addConverterFactory(GsonConverterFactory.create(
                GsonBuilder().serializeNulls().create()
            ))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create()).build()
        return retrofit.create(T::class.java)
    }

    private fun getRequest(response: Response, token: String): Request {
        return response.request
            .newBuilder()
            .removeHeader("Authorization")
            .addHeader("Authorization", "Bearer $token")
            .build()
    }
}

토큰을 체크하고 새로운 토큰을 발급받아 교체만 해주면 됩니다.

여기서 주의할점은 토큰 재발급시 새로운 okhttp의 빌더를 만들고나서 authenticator는 추가하지 말아야합니다.

토큰을 발급받는 api마져 401 error를 받게 된다면 무한루프로 stackoverflow가 발생할 여지가 있기 때문입니다.

댓글