๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๋นˆ ๊ตฌ๋ฉ ์ฑ„์šฐ๊ธฐ

[Android][Retrofit][OAuth] OAuth2.0 ์ธ์ฆ ์ฒ˜๋ฆฌ๋กœ ๋ณ€๊ฒฝ

์„œ๋ฒ„์—์„œ OAuth2.0 ์ธ์ฆ ๋„์ž…์œผ๋กœ ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ์˜ ์‚ฌ์ธ์ธ ๋ฐ api ํ†ต์‹  ๋ฐ ์—๋Ÿฌ์ฒ˜๋ฆฌ์— ์ˆ˜์ • ์‚ฌํ•ญ์ด ์ƒ๊ฒผ๋‹ค.

 

OAuth2.0 

https://oauth.net/2/

** OAuth2.0 ๋ฌธ์„œ๋ฅผ ์ฝ๊ณ  ์ถ”๊ฐ€ํ•  ์˜ˆ์ •

 

 

 

Retrofit ์—์„œ์˜ ์ฒ˜๋ฆฌ

 

API ํ†ต์‹  ์‹œ, Access Token์„ Header์— ๋„ฃ์–ด์ฃผ๊ณ  ์žˆ๊ณ , ์ด ์ฒ˜๋ฆฌ๋Š” OkHttp์˜ interceptor๋ฅผ ์‚ฌ์šฉํ•ด ์ฒ˜๋ฆฌํ•œ๋‹ค.

https://square.github.io/retrofit/

์‚ฌ์ธ์ธ ํ•˜๋ฉด์„œ ๋ฐ›๋Š” ํ† ํฐ ์ •๋ณด์— ํ† ํฐ์˜ ์œ ํšจ ๊ธฐ๊ฐ„ ์ •๋ณด๋„ ์•Œ๋ ค์ฃผ๋‚˜, ์ด๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ๋ณด๋‹ค๋Š” API ํ†ต์‹  ์‹œ์— ๋ฐ›๋Š” access token ๋งŒ๋ฃŒ ์—๋Ÿฌ์ฝ”๋“œ๋ฅผ ๋ฐ›์•„ access token์„ ๊ฐฑ์‹  ํ›„ ๋‹ค์‹œ ํ•ด๋‹น API ํ†ต์‹ ์„ ์ง„ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

OkHttpClient์— addํ•˜๋Š” ๊ธฐ์กด interceptor๋Š” ํ—ค๋”์— access token์„ ์‚ฝ์ž…ํ•˜๋Š” ์—ญํ• ์„ ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ์ด interceptor์—์„œ reponse์˜ ์—๋Ÿฌ์ฝ”๋“œ๋ฅผ ํŒŒ์‹ฑํ•ด ๋‹ค์Œ๊ณผ ๋กœ์ง์„ ํƒ€๋„๋ก ์ˆ˜์ •ํ–ˆ๋‹ค.

A) access token ๋งŒ๋ฃŒ ์—๋Ÿฌ์ฝ”๋“œ๊ฐ€ ๋‚ด๋ ค์˜ค์ง€ ์•Š์œผ๋ฉด ๋ฐ›์€ reponse๋ฅผ ๋ฆฌํ„ดํ•ด์ฃผ๊ธฐ

B) access token ๋งŒ๋ฃŒ ์—๋Ÿฌ์ฝ”๋“œ๊ฐ€ ๋‚ด๋ ค์˜ค๋ฉด access token์„ ๊ฐฑ์‹ ํ•ด์„œ ํ•ด๋‹น API ํ†ต์‹ ์˜ response๋ฅผ ๋ฆฌํ„ดํ•ด์ฃผ๊ธฐ

C) access token ๋งŒ๋ฃŒ ์—๋Ÿฌ์ฝ”๋“œ๊ฐ€ ๋‚ด๋ ค์˜ค๋ฉด access token์„ ๊ฐฑ์‹ ํ•˜์ง€๋งŒ refresh token ๋งŒ๋ฃŒ ์—๋Ÿฌ๋กœ ์‹คํŒจํ•  ๊ฒฝ์šฐ 

C-1) ์ด๋ฉ”์ผ ๊ณ„์ •์˜ ๊ฒฝ์šฐ ๋กœ๊ทธ์•„์›ƒํ•ด์„œ ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ง„์ž…ํ•˜๊ธฐ

C-2) SNS ๊ณ„์ •์˜ ๊ฒฝ์šฐ SNS ์„œ๋น„์Šค์˜ refresh token์„ ๋ฐ›์•„ ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•˜๊ณ  ํ•ด๋‹น API ํ†ต์‹ ์˜ response๋ฅผ ๋ฆฌํ„ดํ•ด์ฃผ๊ธฐ

 

๊ตฌํ˜„ ๊ณผ์ •์—์„œ ๋งˆ์ฃผํ•œ ๋ฌธ์ œ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

Q1. ์—๋Ÿฌ์‘๋‹ต ํŒŒ์‹ฑ์„ ์œ„ํ•ด ํ•œ ๋ฒˆ ์†Œ๋น„ํ•œ body๋Š” ์žฌ์‚ฌ์šฉ์ด ๋ถˆ๊ฐ€ํ•˜๋‹ค. ์—๋Ÿฌ์‘๋‹ต ํŒŒ์‹ฑ์ด ๋๋‚œ ํ›„ response๋ฅผ ๋ฆฌํ„ดํ•ด์ค˜์•ผ ํ•˜๋Š”๋ฐ ๋ฌธ์ œ ๋ด‰์ฐฉ.

A1. ์—๋Ÿฌ ์‘๋‹ต ํŒŒ์‹ฑ์„ ์œ„ํ•ด body๋ฅผ ํ•œ๋ฒˆ ์†Œ๋น„ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์ฃผ์ง€ ์•Š์œผ๋ฉด ๋‹ค๋ฅธ ์ธํ„ฐ์…‰ํ„ฐ์—์„œ body ์ ‘๊ทผ ์‹œ ํฌ๋ž˜์‹œ ๋‚œ๋‹ค. ๊ฐ™์€ ์ฝ˜ํ…์ธ ์˜ body๋ฅผ ๊ฐ€์ง„ ์ƒˆ response๋ฅผ ๋งŒ๋“ค์–ด ๋ฆฌํ„ดํ•ด์ค€๋‹ค.

class MyInterceptor : Interceptor {
    override fun intercept(chain : Interceptor.Chain): Response {
        val request = chain.request().newBuilder().addHeader("key",accessToken).build()
        
        val originalResponse = chain.proceed(request)
        
        val body = originalResponse.body ?: return originalResponse
        val bodyString = body.string()
        
        val newResponse = originalResponse.newBuilder().body(bodyString.toResponseBody(body.contentType())).build()
        
        ....
        //์—๋ŸฌํŒŒ์‹ฑ ํ›„ ๋ฌธ์ œ ์—†๋Š” ๊ฒฝ์šฐ
        return newResponse
    }
}

 

Q2. ์—๋Ÿฌ์ฝ”๋“œ ํŒŒ์‹ฑ์€ ์–ด๋–ป๊ฒŒ ํ•˜๋‚˜. ์ธํ„ฐ๋„ท์˜ ์˜ˆ์ œ๋“ค์„ ๋ณด๋ฉด repsonse์˜ code ๊ฐ’์œผ๋กœ 200 ์™ธ์˜ ๊ฐ’๋“ค์„ ํ™•์ธํ•ด์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ƒํƒœ๋ฅผ ํ™•์ธํ•ด์„œ ์ง„ํ–‰ํ•˜๋˜๋ฐ. ์šฐ๋ฆฌ ์„œ๋ฒ„๋Š” code๋ฅผ 200์œผ๋กœ ๋‚ด๋ ค์ฃผ๊ณ  response body ์•ˆ์— status์™€ error code ๊ฐ’์„ ๋”ฐ๋กœ ์ œ๊ณตํ•ด์ค˜์„œ ์˜ˆ์ œ๋“ค์ฒ˜๋Ÿผ ๊ตฌํ˜„ํ•  ์ˆ˜๊ฐ€ ์—†๋‹ค. ์ด๊ฑฐ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜๊ฒŒ ํŒŒ์‹ฑํ•  ๋ฐฉ๋ฒ•์€ ์—†๋‚˜?

A2. Gson์„ ์ด์šฉํ•œ๋‹ค.

//Data : ์„œ๋ฒ„์—์„œ ๋ฐ›๋Š” response body์˜ form.

val type = object : TypeToken<Data<*>>() {}.type

val errorCode : Int = try {
    val data = Gson().fromJson<Data<*>>(bodyString, type) ?: throw JsonSyntaxException()
    
    if(data.status == 200) return newResponse else data.errorCode
} catch (e: JsonSyntaxException) {
    return newResponse
} catch (t: Throwable) {
    return newResponse
}

 

Q3. ์ด๋ฉ”์ผ ๊ณ„์ •์ด refresh token ๋งŒ๋ฃŒ๋กœ access token ๊ฐฑ์‹ ์— ์‹คํŒจํ•˜๋ฉด ๋กœ๊ทธ์•„์›ƒํ•˜๊ณ  ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด ์ฒ˜๋ฆฌ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜์ง€? API ํ†ต์‹ ์‹œ ์ด interceptor ์™ธ์— ๋‹ค๋ฅธ interceptor๋“ค๋„ ์กด์žฌํ•˜๊ณ , ์ˆ˜๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ฑฐ์ณ๊ฐ„ ํ”„๋กœ์ ํŠธ๋ผ์„œ API ํ†ต์‹  ์„œ๋น„์Šค ๊ธฐ๋Šฅ์ด ํ†ต์ผ๋˜์ง€ ์•Š๊ณ  ๋‹ค์–‘ํ•œ ์ƒํƒœ์ด๋‹ค. ์—๋Ÿฌ ๊ณตํ†ต์ฒ˜๋ฆฌ๊ฐ€ ์ž˜ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์€ ์ƒํƒœ์—์„œ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ด ์žˆ์„๊นŒ? 

A3. ์ด๋ฉ”์ผ ๊ณ„์ •์ด refresh token ๋งŒ๋ฃŒ ์—๋Ÿฌ ์ฝ”๋“œ๋ฅผ ๋ฐ›์œผ๋ฉด custom exception๋ฅผ throw ์‹œํ‚ค์ž. custom exception์„ ๋ฐ›์œผ๋ฉด ๋กœ๊ทธ์•„์›ƒ์‹œํ‚ค๊ณ  ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ๋Œ์•„๊ฐ€๋„๋ก, Throwable์— ํ™•์žฅํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์„œ API ํ†ต์‹ ์‹œ exception ์ฒ˜๋ฆฌ๋กœ ์ ‘๊ทผํ•˜์ž. ๋ชจ๋“  API ํ†ต์‹  ๋ถ€๋ถ„๋“ค์„ ์‚ดํŽด์•ผ๋Š” ํ•˜๊ฒ ์ง€๋งŒ, throwable์— ํ™•์žฅํ•จ์ˆ˜๋ฅผ ์“ฐ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

 

Q4. SNS ๊ณ„์ •์ด refresh token ๋งŒ๋ฃŒ ์‹œ์—๋Š” SNS ๋กœ๊ทธ์ธ์„ ์‹œ๋„ํ•ด์•ผ ํ•œ๋‹ค. kakao๋‚˜ facebook ๊ฐ™์€ SNS library์—์„œ๋Š” callback์œผ๋กœ token ๊ฐ€์ ธ์˜ค๊ธฐ ๋ฐ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ์–ด์„œ ๋น„๋™๊ธฐ๋กœ ๋™์ž‘ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋™๊ธฐ๋กœ token ๊ฐ€์ ธ์˜ค๊ธฐ, ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด๋‹ค. callback์ด ์‹คํ–‰๋œ ์ดํ›„์— ๋‹ค์Œ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋„๋ก ๋™๊ธฐ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ธ๊ฐ€?

A4. CountDownLatch๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

//์นด์นด์˜ค์ชฝ 

val latch = CountDownLatch(1)
UserApiClient.instance.me { user: User?, error: Thrawable? -> 
    if (error != null) {
       ...
       latch.countDown()
    } else if (user != null) {
       ...
       latch.countDown()
    }
}
latch.await()