Skip to content

토큰 만료 시 재인증 하는 방법

Sieun Ju edited this page Feb 12, 2022 · 8 revisions

요약 (Summary)

  • OkHttp 제공하는 Authenticator 인터페이스를 사용하여 토큰 만료시 자동으로 재 인증처리를 구현합니다.

배경 (Background)

  • 사용자가 JWT 토큰을 사용하면 만료되는 시간이 있습니다. 그 안에 앱을 접속 하면 서버에서 RefreshToken을 발급 받아 사용하게 되지만, 이후에 접속하면 토큰이 만료되는 코드인 ‘401’ 코드를 뱉게 됩니다.
  • 토큰 만료시 보통 로그인 페이지로 이동처리하게 되는데 이러한 과정은 사용자에게 좋지 않는 결과를 뱉게 됩니다.

학습한 내용 정리

  • OkHttp 에서 제공하는 Authenticator Interface 를 사용하여 토큰 재인증 처리를 구현합니다.
  • authenticate 함수안에 매개변수인 Response 에서 상태 코드값을 가져와서 재인증 처리를 구현합니다.
  • Authenticator 클래스를 생성할때 생성자로 재인증 하는 API Service 를 매개변수로 가져옵니다. 필요에 따라서 토큰을 발급을 받으면 저장하고 관리할수 있는 LoginManager 도 추가로 가져옵니다.
  • Rx 에서 지원하는 blockingGet 함수를 통해 API 통신을 동기적으로 처리해서 토큰을 가져오도록 처리합니다.

코드 예시 (Example)

com.til.data.interceptor.TokenAuthenticator.kt

override fun authenticate(route: Route?, response: Response): Request? {
        // Token Expired JWT 토큰 만료시 코드 => 401
        return if (response.code == 401) {
            val tokenResponse = apiService.tokenRefresh().blockingGet()
            // Token 저장
            loginManager.setToken(tokenResponse.data?.token ?: "")
            response.request.newBuilder().apply {
                header(NetworkConfig.HEADER_KEY_ACCEPT, NetworkConfig.HEADER_VAL_ACCEPT)
                header(NetworkConfig.HEADER_KEY_CONTENT, NetworkConfig.HEADER_VAL_CONTENT)
                header(NetworkConfig.HEADER_KEY_TOKEN, loginManager.getToken())
            }.build()
        } else {
            null
        }
    }

테스트 (Test)

  • 테스트 진행 방식

    • 만료된 토큰을 발급 받은 후 아무 API 요청했을때 정상적으로 받아 오는지 테스트합니다.
    • 테스트 서버가 만료된 토큰 판단하는 기준은 Header 에서 ‘token’ 값중에 ‘Expired’ 라는 값이 있으면 ‘401’ 코드를 리턴하도록 처리 했습니다.
  • 만료된 토큰을 헤더에 셋팅하고 (https://til.qtzz.synology.me/api/test) API 요청합니다.

    --> GET <https://til.qtzz.synology.me/api/test>
    accept: application/json
    Content-Type: application/json
    token: Token Expired 1642164973740
    --> END GET
    HTTP CODE (401)
    // 토큰 재인증 API 요청
    --> POST <https://til.qtzz.synology.me/api/auth/refresh>
    Content-Length: 0
    accept: application/json
    Content-Type: application/json
    --> END POST (0-byte body)
    <-- 200 <https://til.qtzz.synology.me/api/auth/refresh> (112ms)
    content-type: application/json; charset=utf-8
    content-length: 58
    {"status":true,"data":{"token":"Token Migane Koda Dewdy"}}
    <-- END HTTP (58-byte body)
    // 토큰을 새로 발급 받은후 에러로 리턴받은 API를 재 호출합니다.
    <-- 200 <https://til.qtzz.synology.me/api/test> (229ms)
    content-type: application/json; charset=utf-8
    content-length: 215
    {"status":true,"data":{"integer":1642164978292,"str":"TIL Message"}}
    
    
  • 추가로 성능 테스트를 해봤습니다.

    • 실제로 서비스 할때에는 앱 처음 진입시 토큰이 만료 된경우 토큰을 새로 받아 처리해서 앱 실행 중간에 토큰이 만료되는 경우는 없지만, 모든 변수는 생각해야 하기때문에 멀티 쓰레드 환경에서 중간에 토큰이 만료 됐을때 오류 없이 Response 을 받는지 테스트 해봤습니다.
    • 결과부터 말씀 드리자면 성공적으로 HTTP 통신이 이루어졌습니다. 다만, 멀티 쓰레드 환경에서 중간에 토큰이 만료되기때문에 ‘리프레시 토큰 API’ 를 여러번 호출하고 토큰에 대한 값이 변경이 되었지만 이건 그렇게 심각한 문제가 되어 보이진 않았습니다. (서버쪽에 이슈가 생길지도...?)
    • 멀티 쓰레드 환경에서 중간에 토큰을 만료 시켰을때 다른 HTTP 통신이 어떻게 이루어지는지 테스트 해봤습니다.
    • 코드 예시

참조 (References)

멀티 쓰레드 환경에서 토큰 만료 시키기

private fun multiApiInterval() {
        Flowable.interval(0, 100, TimeUnit.MILLISECONDS)
            .doOnSubscribe {
                if (Random.nextBoolean()) {
                    expiredToken()
                }
            }
            .onBackpressureBuffer()
            .take(10)
            .flatMap { multiApi() }
            .doOnComplete {
                JLogger.d("MultiApi SUCCESS")
            }
            .subscribe({

            }, {
                JLogger.e("ERROR $it")
            }).addTo(compositeDisposable)
    }

    private fun multiApi(): Flowable<Any> {
        return Single.merge(
            getJSendUseCase(),
            getJSendWithMetaUseCase(),
            getJSendListUseCase(),
            getJSendListWithMetaUseCase()
        )
    }
Clone this wiki locally