GDG on Campus: SSWU 6th/Winter Blog Challenge

[Winter Blog Challenge] 안드로이드 [Kotlin] 카카오 로그인 구현 (Chapter Member 박주연)

gdgoc-sswu 2025. 2. 24. 03:24

현재 안드로이드 프로젝트를 진행 중에 있기 때문에, 프로젝트를 하면서 도움이 될 수 있는 카카오 소셜 로그인 구현 방법에 대해서 포스팅을 해보겠습니다!

 

 

0️⃣ 카카오 로그인 과정과 시퀀스 다이어그램

아래의 사진을 참고해주세요!

 

 

1️⃣ 카카오 Developers 세팅

 

1. https://developers.kakao.com/ 에서 회원가입하고, 로그인을 한다.

2. '내 어플리케이션' 클릭 -> 어플리케이션을 추가한다.

3. 일단 고유한 해시키를 알아야한다.

4. 터미널에 아래 코드 입력하면 해시키를 알 수 있다!

keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -storepass android -keypass android | openssl sha1 -binary | openssl base64

5. 그리고 나서 릴리즈키도 구해준다.

6. 터미널에 아래 코드를 입력해서 릴리즈키를 얻을 수 있다.

keytool -exportcert -alias release -keystore /Users/joanna/keystore/release.keystore -storepass yourpassword | openssl sha1 -binary | openssl base64

7. 그럼 카카오 디벨로퍼에서 앱키 목록들을 확인할 수 있는데, 우리가 사용할 것은 네이티브 앱 키 이다.

 

카카오 Developers 세팅을 끝냈으면 이제 안드로이드 스튜디오 설정을 해야한다!

 

2️⃣ 안드로이드 스튜디오 설정

 

공식문서에 아주 자세하게 설명이 나와있으므로 참고하자. (https://developers.kakao.com/docs/latest/ko/android/getting-started) AndroidManifest.xml

 

1. 일단 AndroidManifest에 인터넷을 사용할 수 있는 권한을 추가해준다.

//AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

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

    <application
    
    ...

2. 공식 문서에 나와있는 대로 Gradle을 설정한다.

//settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url = java.net.URI("https://devrepo.kakao.com/nexus/content/groups/public/") }
    }
}
//build.gradle.kts(Module)
dependencies {
  implementation "com.kakao.sdk:v2-all:2.20.6" // 전체 모듈 설치, 2.11.0 버전부터 지원
  implementation "com.kakao.sdk:v2-user:2.20.6" // 카카오 로그인 API 모듈
  implementation "com.kakao.sdk:v2-share:2.20.6" // 카카오톡 공유 API 모듈
  implementation "com.kakao.sdk:v2-talk:2.20.6" // 카카오톡 채널, 카카오톡 소셜, 카카오톡 메시지 API 모듈
  implementation "com.kakao.sdk:v2-friend:2.20.6" // 피커 API 모듈
  implementation "com.kakao.sdk:v2-navi:2.20.6" // 카카오내비 API 모듈
  implementation "com.kakao.sdk:v2-cert:2.20.6" // 카카오톡 인증 서비스 API 모듈
  
  ...
  
}

 

3️⃣ 초기화

 

1. 카카오 Android SDK를 사용하기 위해서 가장 먼저 우리가 전에 받아놓은 네이티브 앱 키로 초기화를 해줘야한다.

GlobalApplication 코틀린 클래스를 하나 생성하고, 초기화 코드를 넣어준다.

//FlobalApplication.kt
class GlobalApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // Kakao SDK 초기화
        KakaoSdk.init(this, "네이티브 앱 키를 여기에 넣으면 된다")
    }
}

 

2. AndroidManifest.xml의 application에도 Kakao SDK 초기화를 수행한 클래스의 이름을 설정한다. GlobalApplication 클래스에서 초기화를 했으므로 아래와 같이 동일한 이름을 설정에 추가해준다.

//AndroidManifest.xml
<application
    <!-- android:name 설정 -->
    android:name=".GlobalApplication"
    ...
>

 

그럼 초기화 완료!

 

4️⃣ Redirect URI 설정

  • AndroidManifest.xml에 들어가서 android:name=".GlobalApplication" 코드와 meta-data 코드를 추가해준다. (name은 그대로 적으면 되고, value 값은 네이티브 앱 키 값이다.)
<meta-data
    android:name="com.kakao.sdk.AppKey"
    android:value="네이티브 앱 키 적으면 된다"/>

 

  • 카카오 로그인 기능을 구현하기 위해서는 리다이렉션(Redirection)을 통해 인가 코드를 받아야 하기 때문에 코드를 추가해준다. (🚨주의: 네이티브 앱 키 앞에 kakao를 붙여주어야 오류가 안난다.)
<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>

 

💡 네이티브 앱 키는 정말 중요하기 때문에 함부로 공개되면 안된다. 꼭 가려주자! 깃허브에 올릴 때에도 .gitignore에 추가해서 숨겨줘야 한다.

 

최종 AndroidManifest.xml 파일 참고!

 

5️⃣ AuthCodeHandlerActivity

  • 매니페스트에 맞는 액티비티를 생성한다. > AuthCodeHandlerActivity 생성하고 코드 작성 해준다.
//AuthCodeHandlerActivity.kt
class AuthCodeHandlerActivity : AppCompatActivity() {

    private val mCallback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
        if (error != null) {
            Log.e(TAG, "로그인 실패 $error")
        } else if (token != null) {
            Log.e(TAG, "로그인 성공 ${token.accessToken}")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

// 카카오톡 설치 확인
        if (UserApiClient.instance.isKakaoTalkLoginAvailable(this)) {
            // 카카오톡 로그인
            UserApiClient.instance.loginWithKakaoTalk(this) { token, error ->
                // 로그인 실패 부분
                if (error != null) {
                    Log.e(TAG, "로그인 실패 $error")
                    // 사용자가 취소
                    if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
                        return@loginWithKakaoTalk
                    }
                    // 다른 오류
                    else {
                        UserApiClient.instance.loginWithKakaoAccount(
                            this,
                            callback = mCallback
                        ) // 카카오 이메일 로그인
                    }
                }
                // 로그인 성공 부분
                else if (token != null) {
                    Log.e(TAG, "로그인 성공 ${token.accessToken}")
                }
            }
        } else {
            UserApiClient.instance.loginWithKakaoAccount(this, callback = mCallback) // 카카오 이메일 로그인
        }
    }
}

 

6️⃣이제 로그인을 구현하고자 하는 프래그먼트에 들어가서 코드를 작성해준다.

 

아래의 코드 참고해주세요! (액티비티에 작성해도 되지만, 지금은 프래그먼트에 작성했다.)

//LoginFragment.kt
class LoginFragment : BaseFragment<FragmentLoginBinding>() {

    companion object {
        fun newInstance() = LoginFragment()
    }
    // 카카오 로그인
    // 카카오계정으로 로그인 공통 callback 구성
    // 카카오톡으로 로그인 할 수 없어 카카오계정으로 로그인할 경우 사용됨
    val callback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
        if (error != null) {
            Log.e(TAG, "카카오계정으로 로그인 실패", error)
        } else if (token != null) {
            Log.i(TAG, "카카오계정으로 로그인 성공 ${token.accessToken}")
            GoMain()
        }
    }
    override fun initView() {
        val context : Context = requireContext()
        // 버튼 클릭했을 때 로그인
        with(binding) {
            KakaoLoginBtn.setOnClickListener {

                // 카카오톡이 설치되어 있으면 카카오톡으로 로그인, 아니면 카카오계정으로 로그인
                if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) {
                    UserApiClient.instance.loginWithKakaoTalk(context) { token, error ->
                        if (error != null) {
                            Log.e(TAG, "카카오톡으로 로그인 실패", error)

                            // 사용자가 카카오톡 설치 후 디바이스 권한 요청 화면에서 로그인을 취소한 경우,
                            // 의도적인 로그인 취소로 보고 카카오계정으로 로그인 시도 없이 로그인 취소로 처리 (예: 뒤로 가기)
                            if (error is ClientError && error.reason == ClientErrorCause.Cancelled) {
                                return@loginWithKakaoTalk
                            }

                            // 카카오톡에 연결된 카카오계정이 없는 경우, 카카오계정으로 로그인 시도
                            UserApiClient.instance.loginWithKakaoAccount(context, callback = callback)
                        } else if (token != null) {
                            Log.i(TAG, "카카오톡으로 로그인 성공 ${token.accessToken}")
                            GoMain()
                        }
                    }
                } else {
                    UserApiClient.instance.loginWithKakaoAccount(context, callback = callback)
                }
            }
        }
    }

    private fun navigateToNotesScreen() {
        findNavController().navigate(R.id.action_loginFragment_to_mainFragment)
    }

    override fun getViewBinding(
        inflater: LayoutInflater,
        container: ViewGroup?
    ) = FragmentLoginBinding.inflate(inflater, container, false)

    fun GoMain(){
        // 로그인 -> 메인
        Navigation.findNavController(binding.root).navigate(R.id.action_loginFragment_to_mainFragment)
    }
}
```


**BaseFragment.kt**
```kotlin
abstract class BaseFragment<VB : ViewBinding> : // 추상화 클래스
    Fragment() {
        private var _binding: VB by autoCleaned()
        val binding: VB get() = _binding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = getViewBinding(inflater, container)
        return binding.root
    }

    abstract fun initView()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initView()
    }
    protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB

}

 

7️⃣ API Key 숨기기

 

깃허브에 올리거나 할 때, 중요한 네이티브 앱 키를 숨겨야한다.

 

외부로부터 노출이 되지 말아야할 키값들은 local.properties에 저장해야하고 깃허브에는 올라가면 안된다.

 

Project 단위에 존재하는 .gitignore에 다음과 같이 local.properties를 추가해서 깃허브에는 올라가지 않도록 한다.

 

  • local.properties 파일에 카카오에서 제공하는 네이티브 앱 키값을 작성한다.
  • Module 단위의 build.gradle 파일에 localProperties를 선언한다.

AndroidManifest 파일에 들어갈 키는 manifestPlaceholders라는 프로퍼티를 통해서 정의하고

 

Application에서 카카오 SDK를 선언할 때 사용할 키값은 buildConfigField를 통해서 정의한다.

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

val kakaoApiKey = localProperties.getProperty("kakao_NATIVE_APP_KEY") ?: ""
val nativeAppKey = localProperties.getProperty("kakao_NATIVE_APP_KEY_MANIFEST") ?: ""

android {
    defaultConfig {
    
            ...
    
        // 코드에서 사용 가능하도록 빌드 설정에 추가
        buildConfigField("String", "kakao_NATIVE_APP_KEY", "\"$kakaoApiKey\"")
        // AndroidManifest.xml에서 사용할 수 있도록 설정
        manifestPlaceholders["NATIVE_APP_KEY"] = nativeAppKey
    }
}
...
  • 처음 코드를 작성할 때, 네이티브 앱키를 작성한 GlobalApplication.kt, AndroidManifest.xml 코드를 수정한다.

실제로 아래와 같이 사용된다.

 

그럼 끝-!