안드로이드

소셜 로그인을 적용해보자 -1부-

강한 맷돼지 2022. 9. 5. 02:21

자 이제 또 앱잼시즌이 도래했다 앱잼에서는 소셜로그인을 많이 쓰니까

소셜로그인의 사용법을 알아보고 기타 로그인 관련 처리는 어떻게 하였는지 그리고 예시로서
소셜 로그인 중 카카오와 네이버를 적용해볼 것이다.

 

일단 어느시점에 시작했는가

 

스파크를 할때만해도 너무 응애였기 때문에 토큰관계 이런거 하나도 몰랐고 알려들지도 않았다 그걸 뚝딱뚝딱 만들어내는 연주랑 영권이를 보면서 난 저런거 언제나 할수있냐 하면서 절망했을뿐 근데 막상해보니 별거 아니였다. 쿠쿠루삥뽕

 

최초로 리드미에서 네이버 로그인을 구현하였고 포토서퍼에서 카카오 로그인과 네이버 로그인을 같이 하게 되어 그 부분을 글로 정리해보려한다. 로그인에는 단순 로그인만 시키는것이 아닌 토큰이 만료되었을때 나는 에러인 401에러가 났을시 어떠한 방법으로 재 로그인 시키고 토큰을 다시 발급 받을지에 대한 처리 또한 필요하다(방법또한 제각기 많다). 

이 이후로는 적용시키지 않았지만 더 복잡하게 하려 한다면 리프레쉬 토큰의 적용, 소셜에서 다양한 정보를 받아올수있다(권한을 받는 부분의 처리가 복잡할것이다)

 

또한 서버개발자와 이야기하여 우리 서버에서 엑세스 토큰을 발급받기위해 소셜 로그인에서 받아온 어떤값을 넘겨 줄 것인지, 

소셜에서 받아오는 정보도 어디까지 서버에서 알아서 받아와서 처리 할것인가에 대한 논의도 해야한다.

 

포토서퍼에서는 소셜에서 소셜토큰(엑세스토큰) 을 그냥 넘겨주면 알아서 서버에서 이메일과 사용자 이름까지 받아오도록 처리했다(사진이나 다른 정보들을 받아오고 싶다면 클라에서 권한을 받는 작업을 거쳐야 받을수있기 때문에 클라에서 정보를 받아서 넘겨야한다). 결국 그냥 클라이언트에서 할일은 소셜토큰을 받아와서 넘기는것이였다. 그래서 이부분을 아주 간단하게 처리할수있었고 작성하는 예시들도 이정도까지의 처리이다. -> 추가적인 처리를 하고싶다면  코드를 수정해야할것이다. 

 

스파크에서는 사용자 고유값을 넘기는것으로 엑세스토큰을 발급받는 작업을 하였다.

 


 

일단 로그인에대해서 대략적으로 살펴보자

 

최초에 로그인에대해 공부하며 토큰이 어떻게 관리되는지 토큰에는 어떤종류가 있고 서버의 보안에 관한이야기 이런것들을 많이 공부했지만 그런거 하나하나 설명하면 한도끝도 없기에 그냥 그런 토큰들의 종류와 정의에 대해서는 링크만 남겨놓겠다.

https://velog.io/@mraz3068/Refresh-Token%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0

 

[Android] Refresh Token이 필요한 이유

앞선 글에사 구글 소셜 로그인을 프론트단에서 구현해보았는데, 이를 수행하면서 생긴 질문들을 글로 정리해보도록 하였다.우선 백엔드 팀에서 구현한 API JWT 기반 로그인 통신 로직은 다음과

velog.io

  

그래서 간단하게 이야기하자면 우리가 필요한것은 AccessToken 이고 이런 토큰은 일정한 형식을 따르며 이런건 우리 서버에서 발급한다. 

또한 Refresh Token 의 경우 보안을 위해서 만든 토큰이다.

 

근데 현재 내가 구현하는 서비스에서는 간단하게 로그인을 구성하고 보안에 신경 안쓰고있는 무서운 줄타기를 하고있기에 그냥 리프레쉬 토큰은 사용안하고 있다.(리프레쉬 토큰을 사용한다면 로직도 더 복잡해질것이다.)

 

그래서 포토서퍼 서버친구들과 합의를 본것이 딱히 소셜에서 받아올 정보도 없으며 닉네임 등도 설정하지 않기에 그냥 소셜토큰 하나만 보내주면 알아서 엑세스 토큰을 발급해주는것으로 결정했다.


 

그렇다면  클라에서 해야할 작업이 뭘지 순서대로 정리해보자.

 

1. 소셜로그인해서 소셜토큰 받아오기(각 소셜 플랫폼마다 방법이 조금씩 다를것이다.)

 

2. 우리 엑세스 토큰 받아온것 로컬 저장소(나는EncryptedSharedPreferences를 사용했다.)에 넣어주고 그값으로 자동로그인 로직만들어주기 

 

3. 엑세스 토큰 받아온것 okhttp 를 통해서 모든 통신에서 헤더에 붙여주기(글에서는 생략)

 

4. 401에러가 났을때 로그인화면으로 보내버리며 재로그인 시키기

 

이정도가 있을것이다. 자 이제 순서대로 하나하나씩 정복해나가보자

 

 

 


1. 소셜로그인에서 소셜토큰 받아오기(네이버, 카카오)

 

자 우선 소셜토큰을 받아와야할것이다. 일단 소셜 로그인의 대략적인 과정은 요 글을 참고해보자

https://velog.io/@sa833591/%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%84%A4%EC%9D%B4%EB%B2%84-...-API-%EC%9E%91%EC%84%B1%EC%A4%91

 

소셜 로그인(카카오,네이버, ...) API

👋 들어가기전최근 로그인을 할때, Kakao, Naver, Google, Facebook 등 portal 또는 SNS에서 제공하는 오픈 API를 사용하여 구현을 한 사이트를 종종 볼 수 있다.보통 내노라하는 IT 회사에서는 다양한 오픈 A

velog.io

 

이글에 나온 과정들을 통해 어떤 소셜 플랫폼을 사용하더라도 비슷한 과정을 거쳐서 토큰을 얻게된다.


근데 막상 안드로이드로 개발해보면 sdk에서 다 알아서 적용되기 때문에 이런 과정이 있다 정도만 알면될꺼같고

위의 블로그 이야기는 서버에서도 정보를 받아올수있는데 그때 서버가 어떤방식으로 소셜 플랫폼과 소통하는지에 대한 방식이다 결국 우리는 똑똑한 sdk 들이 알아서 다 해준다 저런거 필요없다.

 

자 이제 각각의 플랫폼들에서 소셜토큰을 받아오는 코드를 살펴보자.

 

1.네이버

우선 네이버 디벨로퍼에서 네이버 로그인 api 사용을 신청해야 할 것이다.

그 앞쪽의 과정은 죄다 캡쳐하기 귀찮아서 참고한 블로그를 첨부하겠다.

아 그리고 사실 그냥 네이버 로그인 api 공식문서에 굉장히 친절하게 설명되어있다.

https://onlyfor-me-blog.tistory.com/479?category=808872 

 

[Android] 코틀린으로 네이버 로그인 구현하는 법

사용한 안드로이드 스튜디오는 범블비 버전이다. 만들어야 할 때마다 찾아보기 귀찮아서 쓴다. 먼저 네이버 디벨로퍼에서 네이버 로그인 API 사용 신청을 해야 한다. https://developers.naver.com/main/ NA

onlyfor-me-blog.tistory.com

이블로그에서 이제 네이버 디벨로퍼에서 해야하는 과정들을 다 마무리했다면 (코트를 작성하기 전까지 Client ID와 Client Secret를 제공받기까지의 과정)

 

Client ID와 Client Secret를 제공받을 것이다. 

이것은 외부에 유출되면 안되기에 local properties에 꽁꽁 숨겨놓도록하자.  ->  local properties 에 정보숨기는 방법은 추후에 글을써서 첨부해놓도록 할것이다.

 

자 그렇다면 이제부터 실제적으로 내가 해줘야 할일들과 코드를 어떻게 짰는지 확인해보자

 

1. 의존성추가

const val naverAuth = "com.navercorp.nid:oauth:${Versions.naverAuth}"
const val naverAuth = "5.1.0"

//멀티모듈 버전관리를 build src 모듈을 통해서 해서 이런 코드가 나왔다.
//로그인 모듈인 auth 모듈에만 의존성을 추가해주었다

2. local properties에 Client ID와 Client Secret 값추가

 

local.properties

X_NAVER_CLIENT_ID = "어쩌구 저쩌구"
X_NAVER_CLIENT_SECRET = "어쩌구 저쩌구"

auth모듈 build.gradle.kts

import org.jetbrains.kotlin.konan.properties.Properties

val properties = Properties()
properties.load(project.rootProject.file("local.properties").inputStream())

android {
    buildFeatures {
        dataBinding = true
    }
    defaultConfig {
        buildConfigField(
            "String",
            "X_NAVER_CLIENT_ID",
            properties.getProperty("X_NAVER_CLIENT_ID")
        )
        buildConfigField(
            "String",
            "X_NAVER_CLIENT_SECRET",
            properties.getProperty("X_NAVER_CLIENT_SECRET")
        )
    namespace = "com.photosurfer.android.auth"
}

 local properties 에다가 Client ID와 Client Secret 를 숨겨서 git에 올라가는 불상사가 없도록한다.

 

3. 인터넷 권한추가

<!-- 인터넷 사용 권한 설정-->
<uses-permission android:name="android.permission.INTERNET" />

메니페스트에 인터넷 권한 추가해줘야한다. (당연한 소리)

 

4. UI 작업이 있어야할것이다 -> 버튼만들기.(요거는 글에서는 생략하겠다)

 

5. 네이버 sdk 를 이용해서 웹뷰를 띄우고 네이버 로그인을 통해 소셜토큰 받아오기 

-> 여러개의 소셜로그인을 동시에 진행하기에 SocialLoginManager 라는 네이밍으로 class를 분리하여 작성해서 쉽게 다른 프로젝트 에서도 코드를 가져다 쓸수있도록 해놨다.

auth모듈 폴더링 형태

SocialLoginManager 이용하는 방식은 소셜토큰을 받아와서 어떤 행동을 해야하기 때문에 고차함수 형태로 소셜토큰을 뷰모델에서 업데이트 하는 함수를 매개변수로 전달하고 로그인 버틀을 눌렀을시 start플랫폼Login 함수를 호출해주면 되도록 코드를 통일했다.

 

자 이제 NaverLoginManager 코드를 살펴보자

NaverLoginManager.kt

class NaverLoginManager @Inject constructor(
    @ActivityContext private val context: Context
) {
    lateinit var oAuthLoginCallback: OAuthLoginCallback
        private set

    fun naverSetOAuthLoginCallback(updateSocialToken: (String) -> Unit) {
        oAuthLoginCallback = object : OAuthLoginCallback {
            override fun onSuccess() {
                updateSocialToken(NaverIdLoginSDK.getAccessToken() ?: "")
                // 만약 사용자 정보를 받아온다면 여기다가 코드를 더 추가해야한다.
            }

            override fun onError(errorCode: Int, message: String) {
                Timber.d("$message OAuthLoginCallback 부분에서의 오류 onError")
            }

            override fun onFailure(httpStatus: Int, message: String) {
                Timber.d("$message OAuthLoginCallback 부분에서의 오류 onFailure")
            }
        }
    }

    fun startNaverLogin(updateSocialToken: (String) -> Unit) {
        naverSetOAuthLoginCallback { updateSocialToken(it) }
        NaverIdLoginSDK.initialize(
            context,
            X_NAVER_CLIENT_ID,
            X_NAVER_CLIENT_SECRET,
            CLIENT_NAME
        )
        NaverIdLoginSDK.authenticate(context, oAuthLoginCallback)
    }

    companion object {
        private const val CLIENT_NAME = "PhotoSurfer"
    }
}

 

사실 이제 토큰 받아오는 방법과 그 이외의 유저 정보를 받아오는 부분은 네이버 로그인 공식문서에 자세히 나와있고 

그부분을 manager 로 깔끔하게 분리하는것에 중점을 두고 코드를 짰다.

 

우선 이 코드를 봐보자면 첫째로 내부 코드에서 컨택스트를 요구하기 때문에 hilt를 통해서 activity context를 받아오고있다.

 

그후 로그인을 진행했을때 로그인 성공시 할 행동들을 정의하는 콜백으로 oAuthLoginCallback 를 정의하는데 현재 포토서퍼 에서는 네이버에서 사진이라던지 이런 정보들을 얻어올필요가 없어서 그냥 토큰만 받아오는 코드만 존재한다 -> 고차함수로 받아온 뷰모델에 소셜토큰을 업데이트 하는 함수를 success 시에 토큰값을 넣어서 호출하는 코드를 작성해주었다. 만약 여기서 정보들을 받아오고 싶다면 밑의 예시같은 코드를 추가해야할것이다.

// 네이버 로그인 API 호출 성공 시 유저 정보를 가져온다
NidOAuthLogin().callProfileApi(object : NidProfileCallback<NidProfileResponse> {
	override fun onSuccess(result: NidProfileResponse) {
    	name = result.profile?.name.toString()
    	email = result.profile?.email.toString()
    	gender = result.profile?.gender.toString()
    	Log.e(TAG, "네이버 로그인한 유저 정보 - 이름 : $name")
    	Log.e(TAG, "네이버 로그인한 유저 정보 - 이메일 : $email")
    	Log.e(TAG, "네이버 로그인한 유저 정보 - 성별 : $gender")
	}

onError와 onFailure 같은 경우 네이버에서 제공하는 기본적인 오류처리이지만 딱히 현재 여기서 오류 처리를 할생각이 없었어서 단순히 로그만 찍는것으로 대체했다.

 

다음 startNaverLogin 함수를 살펴보면 일단 naverSetOAuthLoginCallback를 호출해주고

네이버에서 하라는대로NaverLoginSDK를 초기화해주고 authenticate를 호출해주어 웹뷰를 띄우고 로그인을 할 수 있도록 한다.

 

사실 정말 간단하다.  이제 액티비티부분 코드를 살펴보자

LoginActivity.kt

@AndroidEntryPoint
class LoginActivity : BaseActivity<ActivityLoginBinding>(R.layout.activity_login) {

    private val viewModel: LoginViewModel by viewModels()

    @Inject
    lateinit var kakaoLoginManager: KakaoLoginManager

    @Inject
    lateinit var naverLoginManager: NaverLoginManager

    override fun onCreate(savedInstanceState: Bundle?) {
        installSplashScreen()
        super.onCreate(savedInstanceState)
        binding.viewModel = viewModel
        onClickLoginBtn()
        
    }

    private fun onClickLoginBtn() {
        // 카카오 네이버 둘다 소셜토큰 업데이트 해주는 함수를 인자로 넣어주면된다
        with(binding) {
            clKakao.setOnClickListener {
                this@LoginActivity.viewModel.updatePlatform(KAKAO)
                kakaoLoginManager.startKakaoLogin {
                    this@LoginActivity.viewModel.updateSocialToken(it)
                }
            }

            clNaver.setOnClickListener {
                this@LoginActivity.viewModel.updatePlatform(NAVER)
                naverLoginManager.startNaverLogin {
                    this@LoginActivity.viewModel.updateSocialToken(it)
                }
            }
        }
    }
	
    companion object {
        private const val NAVER = "naver"
        private const val KAKAO = "kakao"
    }
}

액티비티에서 코드를 살펴보면 hilt를 통해서 NaverLoginManager를 주입받고

로그인 버튼 클릭시 startNaverLogin 함수에 소셜토큰을 뷰모델에 업데이트 해주는 함수를 전달하여 호출하는 코드만 작성해주면된다.

 

생각보다 엄청 간단하다. 그렇다면 이제 카카오도 살펴보자

 

2.카카오

카카오도 마찬가지로 앞전에 카카오 디벨로퍼 사이트에서 쿵짝쿵짝 해줘야할것들이 있다.

https://greedy0110.tistory.com/142

 

[OAuth] 카카오 로그인 안드로이드 kotlin, coroutine 구현 가이드, 플랫폼 설정 부터 coroutine utility 까지

도입 어떤 프로젝트를 해도 사용자 인증은 중요한 키워드다. 그리고 사용자의 쉬운 접근을 위해서는 소셜 로그인 지원은 거의 필수적이다. 이번에는 카카오 로그인에 대해서 a-to-z까지 따라만

greedy0110.tistory.com

그런 부분은 이 블로그를 참고해서 하면된다

-> 네이버랑은 다르게 컴퓨터마다 키해시를 받아와서 등록해줘야하는것도 있고 조금더 복잡하다.

참고사항 Redirect URI 진짜 아무거나 지정해줘도된다  -> 내 깃헙 프로필로 해놨다 ㅋㅋㅋㅋㅋ

 

그 이후 이제 의존성 추가와 코드부분을 살펴보자

 

1. 의존성추가

네이버 보다 훨씬 복잡하다 

일단 공식문서 보고 따라가면되는데

https://developers.kakao.com/docs/latest/ko/getting-started/sdk-android

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

를 참고해보자

 

처음에 이부분에서 엄청 헤맷는데 

 

bulid.gradle 이나 setting.gradle 중 하나만 해주면 되는것이였는데 둘다 해줘야하는줄 알고 둘다 추가하는 실수를했다.

2022.09.04 현재안드로이드 스튜디오 칩멍크를 쓰고있는데 그냥 프로젝트 수준의 build gradle 에다가 이렇게 한줄만 추가해주면 되는거였다.

 

build.geradle.kts(project)

allprojects {
    repositories {
        google()
        mavenCentral()
        maven(url = "https://devrepo.kakao.com/nexus/content/groups/public/")
    }
}

 

 

다음 카카오 로그인 라이브러리를 가져올때 어짜피 로그인만 사용하기에 두번째에 있는 것만 추가해주면 되었다.(다른 기능을 사용한다면 그에 맞게 추가해줘야할것이다.)

 

근데 추후에 나오겠지만 app모듈과 auth 모듈에서 모두 카카오로그인 api를 가져와야한다.(app 모듈에서 manifests에 액티비티를 추가해줘야하기 때문이다.)

 

build.gradle.kts(app,auth) 

// Kakao Login
implementation(ThirdPartyDependencies.kakaoAuth)

const val kakaoAuth = "com.kakao.sdk:v2-user:${Versions.kakaoAuth}"

const val kakaoAuth = "2.11.0"

이렇게 카카오 로그인 api 를 추가해준다.

 

그리고 네이버와 마찬가지로 카카오에서도 카카오 디벨로퍼에서 기본설정을 마치면 여러가지 키값을 주는데

카카오소셜로그인을 위해서는 네이티브 앱키가 필요하다.

 

그래서 이것 또한 네이버에서 때처럼 local.properties 에 보관하는데 manifest에서 이값을 빼와서 사용할수있도록 "" 값이 빠진 값을 하나더 저장해줘야한다.(localproperties 를 manifest에서 가져와서 사용하기위해 아주 많은 뻘짓을 하고 나는 실패했으나 KxxHyoRim 이 성공하여 나에게 전해주었다.)

 

local.prorerties

KAKAO_NATIVE_APP_KEY = "알랄라살랄라"
KAKAO_NATIVE_APP_KEY_NO_QUOTES = 알랄라살랄라 
//두번째꺼는 "" 가 빠져있다 -> manifests에서 값을 가져와서 쓰기위해 ""를 빼줘야한다

 이값들을 사용하기위해 사용처의 모듈들 build.gradle.kts에서 코드를 작성해줘야한다.

 

build.gradle.kts(app)

import org.jetbrains.kotlin.konan.properties.Properties

val properties = Properties()
properties.load(project.rootProject.file("local.properties").inputStream())

android {
    buildFeatures {
        dataBinding = true
    }
    defaultConfig {
        buildConfigField(
            "String",
            "KAKAO_NATIVE_APP_KEY",
            properties.getProperty("KAKAO_NATIVE_APP_KEY")
        )
        
        //요거는 manifest에서 쓰기위해 넣어주는것
        manifestPlaceholders["NATIVE_APP_KEY"] =
            properties.getProperty("KAKAO_NATIVE_APP_KEY_NO_QUOTES")
    }
    namespace = "com.readme.android"
}

 

build.gradle.kts(auth)

import org.jetbrains.kotlin.konan.properties.Properties

val properties = Properties()
properties.load(project.rootProject.file("local.properties").inputStream())

android {
    buildFeatures {
        dataBinding = true
    }
        buildConfigField(
            "String",
            "KAKAO_NATIVE_APP_KEY",
            properties.getProperty("KAKAO_NATIVE_APP_KEY")
        )
    }
    namespace = "com.readme.android.auth"
}

이렇게 코드를 추가해주자.

 

2.pro guard 설정(선택) 

릴리즈 할때 난독화를 대비해서 미리 프로가드를 설정해두자

-keep class com.kakao.sdk.**.model.* { <fields>; }
-keep class * extends com.google.gson.TypeAdapter

요거를 auth와 app 둘다 설정해주면된다.

 

3.manifest에 AuthcodeHandlerActivity 추가하기

<activity
    android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <!-- Redirect URI: "kakao${NATIVE_APP_KEY}://oauth" -->
        <data
            android:host="oauth"
            android:scheme="kakao${NATIVE_APP_KEY}" />
    </intent-filter>
</activity>

이렇게 manifest에다가 ""가 없는 네이티브 키를 추가해서 scheme에서 사용할수있다(앞에 kakao도 붙어야한다.)

 

4.KakaoSdk 초기화

 

READMEapplication.kt

@HiltAndroidApp
class READMEApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            Timber.plant(READMEDebugTree())
        }
        KakaoSdk.init(this, KAKAO_NATIVE_APP_KEY)
    }
}

여기서 sdk를 초기화 해준다 이때 gradle에서 버전이 설정되어있어야하기 때문에 common.gradle에 versionName를 설정해준다

 

common.gradle

def hasLibraryPlugin = pluginManager.hasPlugin("com.android.library")
def hasApplicationPlugin = pluginManager.hasPlugin("com.android.application")

if (hasLibraryPlugin || hasApplicationPlugin) {
    android {
        compileSdk = Constants.compileSdk
        buildToolsVersion = "30.0.3"
        defaultConfig {
            targetSdk = Constants.targetSdk
            minSdk = Constants.minSdk
            if (hasLibraryPlugin) {
                consumerProguardFiles("consumer-rules.pro")
            }
            testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
            //요 버전을 추가해준다
            versionName = Constants.versionName
        }

        compileOptions {
            sourceCompatibility = JavaVersion.VERSION_11
            targetCompatibility = JavaVersion.VERSION_11
        }
        kotlinOptions {
            jvmTarget = JavaVersion.VERSION_11
        }
    }
}

이러면 카카오도 준비소셜 토큰을 받아올 준비가 끝난다.

 

5. social token 받아오기 

네이버에서 SocialLoginManager에 대해서는 충분히 설명했고 네이버와 같은 방식으로 동작하도록 설계했으므로 코드를 보고 간단하게 설명하도록 하겠다. 네이버와 마찬가지로 startKakaoLogin 함수에 뷰모델에서 소셜토큰을 업데이트하는 함수를 전달하면 되며 각각의 함수를 설명해보도록하겠다.

 

package com.readme.android.auth.social_login_manager

import android.content.Context
import com.kakao.sdk.auth.model.OAuthToken
import com.kakao.sdk.common.model.ClientError
import com.kakao.sdk.common.model.ClientErrorCause
import com.kakao.sdk.user.UserApiClient
import dagger.hilt.android.qualifiers.ActivityContext
import timber.log.Timber
import javax.inject.Inject

class KakaoLoginManager @Inject constructor(
    @ActivityContext private val context: Context
) {
    private lateinit var kakaoLoginState: KaKaoLoginState
    private lateinit var kakaoLoginCallback: (OAuthToken?, Throwable?) -> Unit

    fun startKakaoLogin(
        updateSocialToken: (String) -> Unit
    ) {
        kakaoLoginState = getKaKaoLoginState()
        kakaoLoginCallback = getLoginCallback(updateSocialToken)

        when (kakaoLoginState) {
            KaKaoLoginState.KAKAO_TALK_LOGIN -> onKakaoTalkLogin(updateSocialToken)
            KaKaoLoginState.KAKAO_ACCOUNT_LOGIN -> onKakaoAccountLogin()
        }
    }

    private fun getKaKaoLoginState(): KaKaoLoginState =
        if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) {
            KaKaoLoginState.KAKAO_TALK_LOGIN
        } else KaKaoLoginState.KAKAO_ACCOUNT_LOGIN

    private fun getLoginCallback(updateSocialToken: (String) -> Unit): (OAuthToken?, Throwable?) -> Unit {
        return { token, error ->
            if (error != null) {
                Timber.d("${error.message} 카카오 계정으로 로그인 실패")
            } else if (token != null) {
                updateSocialToken(token.accessToken)
            }
        }
    }

    private fun onKakaoTalkLogin(updateSocialToken: (String) -> Unit) {
        UserApiClient.instance.loginWithKakaoTalk(context) { token, error ->
            if (error != null) {
                // 카카오톡으로 로그인 실패
                if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
                    return@loginWithKakaoTalk
                }
                onKakaoAccountLogin()
            } else if (token != null) {
                updateSocialToken(token.accessToken)
            }
        }
    }

    private fun onKakaoAccountLogin() {
        UserApiClient.instance.loginWithKakaoAccount(context, callback = kakaoLoginCallback)
    }
}

enum class KaKaoLoginState {
    KAKAO_TALK_LOGIN, KAKAO_ACCOUNT_LOGIN
}

 

- getKakaoLoginState :

카카오톡이 깔려있고 카카오톡을 통해 로그인을 할수있는 상태를 판단하는 함수이다 

카카오톡으로 로그인할수없다면 카카오계정을 통해 로그인을 할수있도록 판단하게 만드는 함수이다.

 

- getLoginCallback :

카카오계정으로 로그인할때 할일들을 callBack 으로 정의하는 함수이다.

 

- onKaKaoTalkLogin :

 카카오톡으로 로그인할수있는 상태라면 카카오톡으로 로그인해서 소셜토큰을 넘기고 그게아니라면 카카오계정으로 로그인하도록 하는 함수이다.

 

- onKakaoAccountLogin :

카카오계정으로 로그인하는 함수이다.

 

이렇게 정리해서 startKakaoLogin 함수를 실행하면 카카오톡으로 로그인할수 있는지 판단한후 불가능하면 카카오 계정으로 로그인하도록 하는 로직이다.

 

-> 카카오 로그인 매니저는 내가 갈겨놓은 코드를 KxxHyoRim 이 아름답게 함수화 시켜주었다. 감사합니다 ㅎㅎ

 

 

이렇게 소셜토큰을 가져오는 네이버와 카카오 코드들을 살펴보았다.

 

자 이제 큰산은 넘었다.

 

이제 이 소셜토큰들을 제물로 바쳐서 받아온 우리 서버의 엑세스 토큰을 sharedpreference 에 저장하고 이것을 검사하여 자동로그인 로직을 짜보도록할것이다.

 

2. 엑세스토큰 sharedPreference에 저장하고 자동로그인 로직 작성하기

뭐 별로 그렇게 다를까 싶긴하지만 이번에는 EncryptedSharedPreference 를 사용하였다 사용법에 대한 설명은 이 블로그를 참고하자

https://onlyfor-me-blog.tistory.com/497

 

[Android] EncryptedSharedPreference란?

쉐어드 프리퍼런스는 보통 알람 설정같은 T/F 값 등의 간단한 값을 저장할 때 쓰곤 한다. 그러나 서버에서 받은 인증 토큰을 저장해야 하는 경우도 있는데, 이 때는 복잡한 암호화, 복호화 함수를

onlyfor-me-blog.tistory.com

 

https://stackoverflow.com/questions/62498977/how-to-create-masterkey-after-masterkeys-deprecated-in-android

 

How to create masterKey after MasterKeys deprecated in Android

I am using the following code to store some information encrypted in my app. val masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) val sharedPreferences = EncryptedSharedPrefe...

stackoverflow.com

 

일단 나는 sharedPreference 를 hilt를 통해 주입받아서 사용하는 방법을 사용한다.

그래서 우선 모듈을 작성하도록했다.

 

LocalPreferenceModule.kt

package com.readme.android.di

import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object LocalPreferencesModule {
    @Provides
    @Singleton
    fun providesLocalPreferences(@ApplicationContext context: Context): SharedPreferences {
        val masterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build()

        return EncryptedSharedPreferences.create(
            context,
            "encryptedShared",
            masterKey,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        )
    }
}

 

그후 로그인 user 관련 정보를 관리하는 datasource 를 작성해서 localpreference에 있는 엑세스토큰,유저 아이디 등등을 get,set하는 함수들을 만들어서 사용하였다.

 

LocalPreferenceUserDataSourceImpl.kt

package com.readme.android.data.local.datasource

import android.content.SharedPreferences
import androidx.core.content.edit
import javax.inject.Inject

class LocalPreferenceUserDataSourceImpl @Inject constructor(
    private val localPreferences: SharedPreferences
) : LocalPreferenceUserDataSource {
    override fun getAccessToken(): String =
        localPreferences.getString(READ_ME_ACCESS_TOKEN, "") ?: ""

    override fun saveAccessToken(accessToken: String) {
        localPreferences.edit { putString(READ_ME_ACCESS_TOKEN, accessToken) }
    }

    override fun getUserNickname(): String =
        localPreferences.getString(USER_NICKNAME, "") ?: ""

    override fun getUserId(): Int =
        localPreferences.getInt(USER_ID, -1)

    override fun saveUserId(userId: Int) {
        localPreferences.edit { putInt(USER_ID, userId) }
    }

    override fun saveUserNickname(userNickname: String) {
        localPreferences.edit { putString(USER_NICKNAME, userNickname) }
    }

    override fun removeAccessToken() {
        localPreferences.edit { remove(READ_ME_ACCESS_TOKEN) }
    }

    override fun removeUserNickname() {
        localPreferences.edit() { remove(USER_NICKNAME) }
    }

    companion object {
        const val READ_ME_ACCESS_TOKEN = "READ_ME_ACCESS_TOKEN"
        const val USER_NICKNAME = "USER_NICKNAME"
        const val USER_ID = "USER_ID"
    }
}

 

이를 통해  리포지토리 혹은 직접 데이터소스를 사용하여 여러곳에서 엑세스 토큰에 접근하거나 저장하도록 사용하였다.

 

 

이제 자동로그인에 대해서 살펴보자

 

사실 자동로그인은 그냥 sharedpreference 에 접근해서 엑세스토큰이 저장되어있는지 여부를 확인하고 저장되어있다면(default값으로 지정한 "" 이 아니라면) 자동으로 로그인시켜주도록 로직을 작성하면된다.

 

우선 자동로그인에대한 상태를 enum으로 만들어주고

AutoLoginConstant.kt

enum class AutoLoginConstant {
    AUTO_LOGIN_SUCCESS, AUTO_LOGIN_FAILURE
}

LoginActivity.kt

private fun autoLogin() {
    val content: View = findViewById(android.R.id.content)
    content.viewTreeObserver.addOnPreDrawListener(
        object : ViewTreeObserver.OnPreDrawListener {
            override fun onPreDraw(): Boolean {
                return if (::autoLoginState.isInitialized) {
                    if (autoLoginState == AUTO_LOGIN_SUCCESS) {
                        moveMainActivity()
                    }
                    content.viewTreeObserver.removeOnPreDrawListener(this)
                    true
                } else {
                    false
                }
            }
        }
    )
}

private fun checkAutoLoginState() {
    if (loginViewModel.getAccessToken() != "") {
        autoLoginState = AUTO_LOGIN_SUCCESS
    } else {
        autoLoginState = AUTO_LOGIN_FAILURE
    }
}

 

위의 코드와 같이 로그인 액티비티에서 엑세스토큰을 체크하고 그에 따라서 상태를 담는 변수에 자동로그인 상태를 업데이트 해준다.

그후 자동로그인 상태에 따라서 액티비티를 이동하는 로직을 작성하면된다.

 

이런 자동로그인을 판별하는 시점이 스플래시가 돌아가고있을때 체크하게되는 경우가 많았는데 기존에는 스플래시를 따로 제작해서 앞에 깔아줬기 때문에 스플래시 액티비티이후 어느 액티비티로 이동할것인지(홈or로그인) 판단할수있는 시간이 주어졌는데 안드로이드 12이후 디폴트로 스플래시가 생겨서 맨처음 진입하는 액티비티가 뜨고난후 자동로그인이 실행되어 깜빡하며 화면전환이 되는것을 막기위해

 

viewTreeObserver를 이용해서 자동로그인 로직이 실행되어 자동로그인이 되는 조건이라면 홈화면으로 넘어가고 난 후 스플래시가 켜지도록 조정하는 로직을 넣었다.

 

그에 대한 자세한 설명은 이 블로그를 참고하면 될것이다.

https://velog.io/@pachuho/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-12-Splash-Screen-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0

 

[Android] 안드로이드 12 Splash Screen 적용하기

🛠 안드로이드 12 출시 한국시간으로 21년 10월 5일, 안드로이드 12가 출시되었습니다. 여러 변경점이 있었지만 그중에서도 앱을 시작하는 Spalsh Screen이 자동으로 생성되는 기능이 생겼습니다. 개

velog.io

 

로그인 글이 생각보다 너무 길어져서 토큰관련 오류처리에 관한것들은 2부로 글을 나눠서 쓰려고한다.

 

2부야 말로 아주 하이라이트라고 할수있다. 얼른 2부쓰러가야지