diff --git a/app/build.gradle b/app/build.gradle index 57ebd699..b5d24897 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,6 +78,8 @@ dependencies { implementation 'com.jakewharton.threetenabp:threetenabp:1.4.4' implementation 'com.prolificinteractive:material-calendarview:1.4.3' implementation 'androidx.recyclerview:recyclerview:1.3.0' + implementation 'com.google.android.datatransport:transport-runtime:3.1.2' + implementation 'com.google.android.gms:play-services-base:18.0.1' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' @@ -108,4 +110,10 @@ dependencies { // implementation group: 'com.kakao.sdk', name: 'kakaolink', version: '1.30.5' implementation "com.kakao.sdk:v2-user:2.5.0" // 카카오 로그인 + + implementation 'com.google.dagger:hilt-android:2.47' + annotationProcessor 'com.google.dagger:hilt-compiler:2.47' + +// implementation "androidx.security:security-crypto:1.0.0" + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 37cac08e..07d27879 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,40 +16,41 @@ + + + - - - - + android:value="$kakao_native_app_key" /> + + @@ -58,11 +59,11 @@ + - - @@ -89,13 +89,14 @@ + android:exported="true" + android:windowSoftInputMode="adjustResize"> + @@ -109,6 +110,7 @@ + @@ -122,11 +124,11 @@ + - @@ -179,11 +181,11 @@ android:exported="true" android:windowSoftInputMode="adjustResize"> - + diff --git a/app/src/main/java/com/eatssu/android/data/RefreshTokenService.kt b/app/src/main/java/com/eatssu/android/data/RefreshTokenService.kt new file mode 100644 index 00000000..42ffceaa --- /dev/null +++ b/app/src/main/java/com/eatssu/android/data/RefreshTokenService.kt @@ -0,0 +1,24 @@ +package com.eatssu.android.data + +import RetrofitImpl.retrofit +import com.eatssu.android.App +import com.eatssu.android.data.service.UserService + +object RefreshTokenService { + + private val userService = retrofit.create(UserService::class.java) + + fun refreshToken(): String { + val res = userService.getNewToken().execute() + if (res.isSuccessful) { + val newAccessToken = res.body()?.accessToken ?: "" + val newRefreshToken = res.body()?.refreshToken ?: "" + if (newAccessToken.isNotBlank()) { + App.token_prefs.accessToken = newAccessToken + App.token_prefs.refreshToken = newRefreshToken + return newAccessToken + } + } + throw IllegalStateException("토큰 재발급 실패") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/eatssu/android/data/RetrofitImpl.kt b/app/src/main/java/com/eatssu/android/data/RetrofitImpl.kt index 9c771267..bd9c1ca1 100644 --- a/app/src/main/java/com/eatssu/android/data/RetrofitImpl.kt +++ b/app/src/main/java/com/eatssu/android/data/RetrofitImpl.kt @@ -1,5 +1,6 @@ import com.eatssu.android.App import com.eatssu.android.BuildConfig +import com.eatssu.android.data.TokenAuthenticator import okhttp3.* import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit @@ -48,6 +49,7 @@ object RetrofitImpl { OkHttpClient.Builder() .addInterceptor(httpLoggingInterceptor) .addInterceptor(AppInterceptor()) + .authenticator(TokenAuthenticator()) .build() } diff --git a/app/src/main/java/com/eatssu/android/data/TokenAuthenticator.kt b/app/src/main/java/com/eatssu/android/data/TokenAuthenticator.kt index c5289fc8..9b2432e8 100644 --- a/app/src/main/java/com/eatssu/android/data/TokenAuthenticator.kt +++ b/app/src/main/java/com/eatssu/android/data/TokenAuthenticator.kt @@ -1,102 +1,25 @@ package com.eatssu.android.data -import android.content.Context -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.runBlocking +import android.util.Log import okhttp3.Authenticator import okhttp3.Request import okhttp3.Response import okhttp3.Route -import retrofit2.Retrofit -//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() -// .refreshToken(RefreshToken(token)) -// .with(rx) -// .response(object : ApiCallback{ -// 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 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() -// } -//} \ No newline at end of file +class TokenAuthenticator: Authenticator { + override fun authenticate(route: Route?, response: Response): Request? { + Log.i("Authenticator", response.toString()) + Log.i("Authenticator", "토큰 재발급 시도") + return try { + val newAccessToken = RefreshTokenService.refreshToken() + Log.i("Authenticator", "토큰 재발급 성공 : $newAccessToken") + response.request.newBuilder() + .removeHeader("Bearer").apply { + addHeader("Bearer", newAccessToken) + }.build() // 토큰 재발급이 성공했다면, 기존 헤더를 지우고, 새로운 해더를 단다. + } catch (e: Exception) { + e.printStackTrace() + null // 만약 토큰 재발급이 실패했다면 헤더에 아무것도 추가하지 않는다. + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/eatssu/android/ui/login/SocialLoginActivity.kt b/app/src/main/java/com/eatssu/android/ui/login/SocialLoginActivity.kt index 638ef6a7..fe129f22 100644 --- a/app/src/main/java/com/eatssu/android/ui/login/SocialLoginActivity.kt +++ b/app/src/main/java/com/eatssu/android/ui/login/SocialLoginActivity.kt @@ -11,6 +11,7 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.eatssu.android.App +import com.eatssu.android.data.MySharedPreferences import com.eatssu.android.data.model.request.loginWithKakaoRequest import com.eatssu.android.data.model.response.TokenResponse import com.eatssu.android.data.service.OauthService @@ -69,6 +70,31 @@ class SocialLoginActivity : AppCompatActivity() { super.onCreate(savedInstanceState) binding = ActivitySocialLoginBinding.inflate(layoutInflater) setContentView(binding.root) + + //카카오 로그인에서 제공하는 자동로그인 메소드 +// UserApiClient.instance.accessTokenInfo { tokenInfo, error -> +// if (error != null) { +// Toast.makeText(this, "자동 로그인 실패", Toast.LENGTH_SHORT).show() +// } +// else if (tokenInfo != null) { +// Toast.makeText(this, "자동 로그인 성공", Toast.LENGTH_SHORT).show() +// val intent = Intent(this, MainActivity::class.java) +// startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) +// } +// } + + // SharedPreferences 안에 값이 저장되어 있을 때-> Login 패스하기 + if (App.token_prefs.accessToken?.isNotBlank() == true){ // SharedPreferences 안에 값이 저장되어 있을 때 -> MainActivity로 이동 + Toast.makeText( + this, + "자동 로그인 되었습니다.", + Toast.LENGTH_SHORT + ).show() + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + finish() + } + val context = this binding.imbKakao.setOnClickListener { lifecycleScope.launch { diff --git a/app/src/main/java/com/eatssu/android/ui/login/UserApiClientExt.kt b/app/src/main/java/com/eatssu/android/ui/login/UserApiClient.kt similarity index 100% rename from app/src/main/java/com/eatssu/android/ui/login/UserApiClientExt.kt rename to app/src/main/java/com/eatssu/android/ui/login/UserApiClient.kt