๋๋์ด ์๋๋ก์ด๋์์ Access Token
์ด ๋ง๋ฃ๋์์ ๋์ Refresh Token
๋ง๋ฃ๋์์ ๋ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์๊ฐํ ์ฐจ๋ก๋ค!!
Access Token
๊ณผ Refresh Token
์ด ์ด๋ค ํ๋ก์ธ์ค๋ก ๋์ํ๋์ง ์ ๋ชจ๋ฅด์๋ ๋ถ๋ค์ ๐ง Access Token๊ณผ Refresh Token์ด๋ ๋ฌด์์ด๊ณ ์ ํ์ํ ๊น? โ ์ด ๊ธ์ ๋จผ์ ๋ณด๊ณ ์ค๋ ๊ฒ ์ข์ ๊ฒ ๊ฐ์ต๋๋ค!
์๊ฐ๋ณด๋ค ๊ธ์ด ๊ธธ์ด์ ธ์ Access Token
๋ง๋ฃ ์ฒ๋ฆฌ์ Refresh Token
๋ง๋ฃ ์ฒ๋ฆฌ ๊ธ์ ๋๋ ์ ์์ฑํ๋ค.
์ค๋์
Access Token
๋ง๋ฃ ์ฒ๋ฆฌ ๊ณผ์ & ์ฝ๋๋ฅผ ๋ค๋ฃจ๋๋ก ํ๊ฒ ๋ค.
์ฐธ๊ณ ๋ก ์๋ ์ฝ๋๋ ์ ๋ถ ์ฝํ๋ฆฐ์ผ๋ก ๊ตฌํ๋์ด์์ต๋๋ค!๐
Access Token์ด ๋ง๋ฃ๋์์ ๋
1๏ธโฃ Refresh Token์ผ๋ก ๋์ฒดํ์ฌ API ์ฌ์์ฒญํ๊ธฐ
2๏ธโฃ ์ Access Token ๋ก์ปฌ(dataStore)์ ์ ์ฅํ๊ธฐ
์์ ๋ชฉํ๋ฅผ ๋ฌ์ฑํ๊ธฐ ์ํด ์ฌ์ฉํ ๊ธฐ์ ๊ณผ ๊ตฌํํ ์์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
OkHttp3
์ Interceptor
์์Authorization
ํค ๊ฐ ์กด์ฌํ๋ฉด Access Token ์
๋ฐ์ดํธ ํด์ฃผ๋ ์์
OkHttp3
์ Authenticator
์์401 http error code
๋ฅผ ๋ฐ์์ ๋ ํธ์ถ๋๋ ํด๋์คAccess Token
๋ง๋ฃ ์ Access Token
โก Refresh Token
์ผ๋ก ๊ต์ฒด ํ API ์ฌ์์ฒญํ๋ ์์
Access Token
์ ๋ก์ปฌ์ ์ ์ฅํด๋๋ ๊ณณCRUD
์์
๋๊ธฐ or ๋น๋๊ธฐ๋ก ์ ํํด์ ์์
๊ฐ๋ฅ๋จผ์ OkHttp์ ๋ํ ์๊ฐ๊ฐ ํ์ํ ๊ฒ ๊ฐ๋ค. OkHttp๋ ์ฑ์์ ๋คํธ์ํฌ ํต์ (์์ฒญ ๋ฐ ์๋ต)์ ํ ๋ ์ฌ์ฉ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค. HTTP๋ฅผ ํจ์จ์ ์ผ๋ก ์ํํ๋ฉด์ ๋ก๋๋ฅผ ๋น ๋ฅด๊ฒ ํ๊ณ ๋์ญํญ์ ์ ์ฝํ๋ ํน์ง์ ๊ฐ์ง๊ณ ์๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก OkHttp3
๋ HTTP/2๋ฅผ ์ง์ํ๊ณ ์๋ค. HTTP/2
๋ HTTP/1.1
๊ณผ ์ ์ฌํ ๊ธฐ๋ฅ์ ํ์ง๋ง ๋ ํจ์จ์ ์ผ๋ก ๋ง๋ ํ๋กํ ์ฝ์ด๋ค.
HTTP/1.1
๊น์ง๋ request์ ๋ํ response๊ฐ ์ค๊ณ ๋ค์ request๋ฅผ ๋ณด๋ผ ์ ์์๋ค. ์ฌ๊ธฐ์ ๋ณ๋ชฉํ์์ด๋ผ๋ ๋ฌธ์ ์ํฉ์ด ๋ฐ์ํ๊ณ ์ด๋ฅผ HOL(Head of line blocking)
์ด๋ผ๊ณ ํ๋ค.HTTP/2
์์๋ ๋ณ๋ ฌ ์ ์ก์ ์ง์ํ๊ฒ ๋์๋ค.HTTP/1.1
์์๋ ํค๋๋ฅผ ํ๋ฌธ์ผ๋ก ๋ณด๋์ง๋ง, HTTP/2
์์๋ ํค๋๋ฅผ ์์ถํด์ ๋ณด๋ด ์ฉ๋ ๋๋น ์ฒ๋ฆฌ ํจ์จ์ฑ์ ๋์๋ค.HTTP๋ก ์๋ฒ-ํด๋ผ์ด์ธํธ ํต์ ์ ํ ๋ Frame
์ ๊ฐ์ง๊ณ ํ๋ค. Frame
์ Header
์ Data
๋ก ๊ตฌ์ฑ๋์ด์๋ค. Header
์ Data
์ ๊ตฌ์กฐ๋ ์๋ ์ฌ์ง๊ณผ ๊ฐ๋ค.
์ฐ๋ฆฌ๊ฐ ๊ด์ฌ์ ๊ฐ์ ธ์ผํ๋ ๊ตฌ์กฐ๋ Header
๋ค. ์์ ๋ธ๋ก๊ทธ์์ ์ด์ผ๊ธฐํ ๊ฒ์ฒ๋ผ JWT ํ ํฐ ์ธ์ฆ ๋ฐฉ์์ Header
์ ์๋ JWT ํ ํฐ์ผ๋ก ์ธ๊ฐ(Authorization)์ ํ์ธํด์ฃผ๋ ์์
์ด๊ธฐ ๋๋ฌธ์ด๋ค.
์ด์ ๋ค์ ๋์๊ฐ์ OkHttp3
๊ฐ ์ง์ํด์ฃผ๋ ํค๋์ ๊ด๋ จ๋ ํด๋์ค๋ฅผ ๊ณต๋ถํด๋ณด์.
Interceptor๋ HTTP
ํต์ ์ ๋ชจ๋ํฐ๋งํ ์ ์๊ฒ ํด์ฃผ๊ณ , API ์ฌ์์ฒญ ๋ฑ์ ๊ฐ๋ฅํ๊ฒ ํด์ฃผ๋ ํด๋์ค์ด๋ค. Interceptor์๋ Application Interceptor
์ Network Interceptor
2๊ฐ์ง๊ฐ ์๋ค.
๐ย Application Interceptor
๐ย Network Interceptor
์ค์ ๋คํธ์ํฌ ์์ฒญ ๋ฐ ์๋ต๊ณผ ๊ด๋ จ๋ ์์ ์ ์ํํ๋๋ฐ ์ฌ์ฉ
์ฌ์ฉ ์) ๋คํธ์ํฌ ์์ฒญ ๋ฐ ์๋ต ๋ก๊ทธ
์ฐ๋ฆฌ๋ Authorization์ ์ฒ๋ฆฌํ ๊ฒ์ด๋ฏ๋ก Application Interceptor
๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
๋จผ์ build.gradle(Module :app)
ํ์ผ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ์ถ๊ฐํด์ค์ผ ํ๋ค. (๋ง๋ฃ ์์
์ ๊ตฌํํ๋ ์ฌ๋์ด๋ผ๋ฉด ์ด๋ฏธ DataStore
๋ฅผ ์ ์ธํ๊ณ ๋ ์ด๋ฏธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํ์ ๊ฑฐ๋ผ ๋ฏฟ๋๋ค!)
dependencies {
// OkHttp
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0"))
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
// DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0"
//Retrofit
def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
//Hilt
def hilt_version = "2.44"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
}
์ธํฐ๋ท ํต์ ํ๊ธฐ ์ ์ Internet ๊ถํ๋ AndroidManifest.xml
์์ ์ถ๊ฐํด์ค์ผํ๋ ๊ฒ๋ ์์ง ๋ง์.
<uses-permission android:name="android.permission.INTERNET" />
JWT ํ ํฐ์ ๋ก์ปฌ์ ์ ์ฅํด์ฃผ๊ธฐ ์ํด DataStore Preference๋ฅผ ์ฌ์ฉํ๋ค. ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ์ฌ ์ธ์ฆ(Authentication)ํ๋ฉด ์๋ฒ๋ก๋ถํฐ Refresh Token
๊ณผ Access Token
์ ๋ฐ๊ฒ ๋๋ค. ์ด๋ TokenManager
์ ์ ์ํ save_token()
ํจ์๋ฅผ ์ฌ์ฉํด์ ๊ฐ Token์ ์ ์ฅํ๋ฉด ๋๋ค.
class TokenManager @Inject constructor(
private val dataStore: DataStore<Preferences>
) {
companion object {
private val ACCESS_TOKEN_KEY = stringPreferencesKey("access_token")
}
fun getAccessToken(): Flow<String?> {
return dataStore.data.map { prefs ->
prefs[ACCESS_TOKEN_KEY]
}
}
suspend fun saveAccessToken(token: String){
dataStore.edit { prefs ->
prefs[ACCESS_TOKEN_KEY] = token
}
}
suspend fun deleteAccessToken(){
dataStore.edit { prefs ->
prefs.remove(ACCESS_TOKEN_KEY)
}
}
}
OkHttp3
์ Interceptor
๋ request header์ ๋ด์ฉ ์ถ๊ฐ์ ๊ต์ฒด ๊ธฐ๋ฅ๋ฅผ ์ ๊ณตํ๋ค. ์ฐ๋ฆฌ๋ JWT ํ ํฐ์ ํค๋์ ๋ฃ์ด์ค์ผํ๋ฏ๋ก Interceptor
๋ฅผ ์์๋ฐ์ AuthInterceptor
ํด๋์ค๋ฅผ ๋ง๋ค๋ฉด ๋๋ค. Interceptor
๋ฅผ ์์ ๋ฐ์ผ๋ฉด interceptor()
ํจ์๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉ ํ๋ผ๊ณ ๋ฌ๋ค.
๊ธฐ๋ณธ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค. (์๋์์ ์ถ๊ฐ๋ ์์ )
class AuthInterceptor @Inject constructor(
private val tokenManager: TokenManager
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token: String = runBlocking {
tokenManager.getAccessToken().first()
} ?: return errorResponse(chain.request())
val request = chain.request().newBuilder().header(AUTHORIZATION, "Bearer $token").build()
return chain.proceed(request)
}
private fun errorResponse(request: Request): Response = Response.Builder()
.request(request)
.protocol(Protocol.HTTP_2)
.code(NETWORK_ERROR)
.message("")
.body(ResponseBody.create(null, ""))
.build()
}
tokenManager
์์ Access Token
์ ๋ฐ์ ์จ๋ค.
1-1. Access Token
์ด null
์ผ ๊ฒฝ์ฐ errorResponse
๋ฅผ return ํด์ค๋ค.
1-2. Access Token
์ JWT ํ ํฐ์ด ๋ค์ด์๋ค๋ฉด 2๋ฒ์ผ๋ก ์ด๋ํ๋ค.
Interceptor
๊ฐ ๊ฐ์ ธ์จ chain
์์ request์ Builder๋ฅผ ๋ง๋ค์ด์ค๋ค.
ํค๋์ Authorization
key์ Bearer๋ฅผ ๋ถ์ฌ Access Token ๊ฐ์ ๋ฃ์ด์ค๋ค.
chain.proceed(requset)
๋ฅผ ํตํด HTTP ์์ฒญ์ ๋ํ ์๋ต์ ๋ง๋ค๊ณ return ํ๋ค.
Access Token
์ ๋ฃ์ด ๋ณด๋๋ request์ Access Token
์ด ๋ง๋ฃ๋์๋ค๋ฉด 401(Unauthorized) http ์ํ ์ฝ๋๋ฅผ ๋ฐ๊ฒ ๋๋ค. ์์ ๋งํ๋ฏ ์ด๋ฅผ ์ฒ๋ฆฌํด์ฃผ๋ ํด๋์ค๊ฐ Authenticator
์ด๋ค. Authenticator
๋ 401์ ๋ฐ์์ ๋๋ง ํธ์ถ๋๊ธฐ ๋๋ฌธ์ด๋ค.
์ด์ AuthAuthenticator
๋ฅผ ๊ตฌํํ์. Authenticator
๋ฅผ ์์๋ฐ์ ํด๋์ค๋ฅผ ๋ง๋ค๋ฉด authenticate()
ํจ์๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํ๋ผ๊ณ ๋ฌ๋ค. authenticate()
ํจ์๋ฅผ ์ฑ์๋ณด์.
๊ตฌํํ ๋ด์ฉ์ Access Token
์ Refresh Token
์ผ๋ก ๊ต์ฒดํ์ฌ API ์ฌ์์ฒญํ๋ ๋ถ๋ถ์ด๋ค.
class AuthAuthenticator @Inject constructor(
private val tokenManager: TokenManager
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
val refreshToken = runBlocking {
tokenManager.getRefreshToken().first()
}
if (refreshToken == null || refreshToken == "LOGIN") {
response.close()
return null
}
return newRequestWithToken(refreshToken, response.request)
}
private fun newRequestWithToken(token: String, request: Request): Request =
request.newBuilder()
.header(AUTHORIZATION, token)
.build()
}
Refresh Token
์ tokenManager
์์ ๊ฐ์ ธ์ค๊ธฐ
1-1. refreshToken
์ด null
์ด๊ฑฐ๋ โLOGINโ
(๋ก๊ทธ์ธ ์ )์ด๋ผ๋ฉด API ์ฌ์์ฒญ request๋ฅผ ๋ง๋ค์ง ์๊ณ ๋ซ์๋ฒ๋ฆฐ๋ค.
1-2. refreshToken
์ JWT ํ ํฐ ๊ฐ์ด ๋ค์ด์์ผ๋ฉด 2๋ฒ์ผ๋ก ์ด๋ํ๋ค.
request ํค๋์ Authorization
๊ฐ์ Refresh Token
์ผ๋ก ๊ต์ฒดํ๊ธฐ
API ์ฌ์์ฒญ
์ ์ฝ๋์์ ์ฃผ๋ชฉํ ๋ถ๋ถ์ runBlocking
์ด๋ค. refreshToken
์ ๊ฐ์ ธ์ฌ ๋ runBlocking
์ ์ฌ์ฉํ๋๋ฐ, ๊ทธ ์ด์ ๋ ๋๊ธฐ์ ์ธ ์์ฐจ ํ๋ฆ์ ๋ง๋ค๊ธฐ ์ํด์๋ค. Refresh Token
์ ๊ฐ์ ธ์จ ๋ค์์ request API๋ฅผ ๋ณด๋ผ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
์ฐธ๊ณ ๋ก .first()
๋ฅผ ํ ์ด์ ๋ dataStore
์ ์ ์ฅ๋ ํ ํฐ์ด flow
๋ก ์ ๋ฌ๋๊ธฐ ๋๋ฌธ์ด๋ค. ํ ํฐ์ ํ๋๋ฐ์ ์์ผ๋ฏ๋ก stream
์์ ์ฒซ๋ฒ์งธ๋ก ๋ค์ด์จ ๊ฐ์ด ํ ํฐ์ด ๋๋ค.
์ด์ ๋ค์ Interceptor
์์ ์๋ก์ด response๋ฅผ ๋ฐ์ ์ฐจ๋ก๋ค. ๋ด๊ฐ ๊ฐ์ง Refresh Token
์ด ์ ํจํ ํ ํฐ์ด๋ผ๋ฉด ์๋ฒ๋ ๋์๊ฒ ์๋ก์ด Access Token
์ ์ ๋ฌํด์ค ๊ฒ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
class AuthInterceptor @Inject constructor(
private val tokenManager: TokenManager
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token: String = runBlocking {
tokenManager.getAccessToken().first()
} ?: return errorResponse(chain.request())
val request = chain.request().newBuilder().header(AUTHORIZATION, "Bearer $token").build()
////////////////////////////////// ์ฌ๊ธฐ๋ถํฐ ์ถ๊ฐ๋ ์ฝ๋ /////////////////////////////////////
val response = chain.proceed(request)
if (response.code == HTTP_OK) {
val newAccessToken: String = response.header(AUTHORIZATION, null) ?: return response
Timber.d("new Access Token = ${newAccessToken}")
CoroutineScope(Dispatchers.IO).launch {
val existedAccessToken = tokenManager.getAccessToken().first()
if (existedAccessToken != newAccessToken) {
tokenManager.saveAccessToken(newAccessToken)
Timber.d("newAccessToken = ${newAccessToken}\nExistedAccessToken = ${existedAccessToken}")
}
}
} else {
Timber.e("${response.code} : ${response.request} \n ${response.message}")
}
return response
}
...
}
response code๊ฐ HTTP_OK(200)
์ธ์ง ํ์ธํ๋ค
response ํค๋์ Authorization
ํค ๊ฐ์ด ์กด์ฌํ๋ ์ง ํ์ธ
2-1. ์กด์ฌํ์ง ์์ผ๋ฉด Refresh Token
์ผ๋ก request๋ฅผ ๋ณด๋ธ ๊ฒ ์๋๋ฏ๋ก ํ ํฐ ์
๋ฐ์ดํธ๊ฐ ํ์์์ผ๋ return response๋ฅผ ํ๊ณ ํจ์๋ฅผ ์ข
๋ฃํ๋ค
2-2. ์กด์ฌํ๋ค๋ฉด 3๋ฒ์ผ๋ก ์ด๋ํ๋ค.
Authorization
์ ๋ค์ด์๋ ์๋ก์ด Access Token
๊ณผ tokenManager
์ ๋ค์ด์๋ Access Token
์ ๋น๊ตํด์ค๋ค.
3-1. ๋์ผํ๋ค๋ฉด ์์ response์์ ์ด๋ฏธ ์ ๋ฐ์ดํธํด์ค ๊ฒ์ด๋ฏ๋ก ๋ฐ๋ก ์ฒ๋ฆฌํด์ค ํ์ ์๋ค.
3-2. ๋์ผํ์ง ์๋ค๋ฉด ์๋ก์ด Access Token
์ด๋ฏ๋ก ์ ์ฅ ์์
์ ์ํํ๋ค.
Q. ์๋ก์ด Access Token
์ ์ ์ฅํ ๋ ๋น๋๊ธฐ(CoroutineScope(Dispatchers.IO)
)์์ ์์
ํ ์ด์ ๋?
A. UI๋ฅผ blockingํ๋ ๊ฒ๋ณด๋ค๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ 2~3๋ฒ ์ฒ๋ฆฌํ๋ ๊ฒ ๋ ๋ซ๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฌผ๋ก ๋น๋๊ธฐ๋ก ์ฒ๋ฆฌํ๋ฉด ๋ณ๋ ฌ์ ์ผ๋ก response๊ฐ ์ค๋ฏ๋ก (HTTP/2
์ ํน์ฑ) ํด๋น ์์
์ด 1๋ฒ ์ด์ ์ํ๋ ์๋ ์๋ค. ๊ทธ๋ฌ๋ ์ด๋ฏธ ๋ฐ์ response์ด๋ฏ๋ก ๋น๋๊ธฐ๋ก ์์
ํ๋ค๊ณ ํด์ ๋คํธ์ํฌ์ ํผ์ ์ด๋ ๋ถํ๋ฅผ ์ฃผ์ง ์๋๋ค. ๋ฐ๋ผ์ runBlocking
์ ์ฌ์ฉํ์ฌ UI๋ฅผ blockingํ๋ฉด์๊น์ง ๋๊ธฐ์ ์ผ๋ก ์์
ํ ํ์๊ฐ ์๋ค๊ณ ๋๊ผ๊ณ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฒ๋ฆฌํ๋๋ก ๊ตฌํํ๋ค.
๊ธฐ์กด์๋ ์ฌ์ค DataStore
๋์ ์ SharedPreference
๋ฅผ ์ฌ์ฉํ์๋ค. ๊ทธ๋ฌ๋ ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก DataStore
๋ฅผ ์ฌ์ฉํด JWT ํ ํฐ์ ์ ์ฅํ๊ธฐ๋ก ํ๋ค.
1. ๋น๋๊ธฐ ์ฒ๋ฆฌ
SharedPreference
์ ๋ฌ๋ฆฌ DataStore
๋ Flow
๋ก ๋ฐ์ดํฐ๋ฅผ stream
์ ์ ์ฅํ๋ค. ๋ฐ๋ผ์ CoroutineScope
๋ด์์ ๋น๋๊ธฐ๋ก ๋ฐฑ๊ทธ๋ผ์ด๋ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค. UI๋ฅผ ๋ง์ง ์๋๋ค๋ ์ ์ด ๊ฐ์ฅ ๋ง์์ ๋ค์๋ค.
2. ์์ ํ๊ณ ํจ์จ์ ์ด๋ค.
์์ง ์์ ์ฝ๋์์ ๋์ค์ง๋ ์์์ง๋ง ๋ก๊ทธ์์ ๊ธฐ๋ฅ์ ์ํํ ๋ ๋ก์ปฌ์ ์ ์ฅ๋ JWT ํ ํฐ์ ๋ค ์ง์์ฃผ์ด์ผํ๋ค. ๊ทธ๋ฐ๋ฐ SharedPreference๋ฅผ ์ฌ์ฉํ๋ฉด UI๋ฅผ ๋ง๊ฑฐ๋ (๋ก๊ทธ์ธ ์ด๋ ํ๋ฉด ์ด๋ ์์
์ ์ ์ด๋ฃจ์ด์ ธ์ผํ๋ฏ๋ก) ์ ๋๋ก ์์ง์์ง๋ ๊ฒฝ์ฐ๊ฐ ์๊ฒผ๋ค. ๋น๋๊ธฐ ์ฒ๋ฆฌ ๋๋ถ์ ์์ ํ๊ณ ํจ์จ์ ์ด๊ฒ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํด์ง ๊ฒ์ด๋ค!
์ฌ์ค Interceptor
๋ง ์ฌ์ฉํด์๋ Access Token
๋ง๋ฃ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ค. ์์์ HTTP_OK
๋ฅผ ํ์ธํ ๊ฒ์ฒ๋ผ ์๋ฌ ์ฝ๋๊ฐ UNAUTHORIZED
์ธ์ง ํ์ธํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋ฐ๋ฐ Refresh Token
๋ง๋ฃ๋ฅผ ์ฒ๋ฆฌํ ๋ ์ด๋ ค์์ ๊ฒช์๊ณ 2๊ฐ๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ํ ๊ฒ์ด๋ค.
์ด ๋ถ๋ถ์ ๋ค์ ์ฑํฐ์์ ์์ธํ ๋ค๋ฃจ๋๋ก ํ๊ฒ ๋ค.
๊ทธ๋ผ ์๋ ํ!!!๐
์ ๋ง ๋ฉ์ง์ธ์