본문 바로가기

Frontend/Kotlin

Solution:카카오 소셜 로그인 구현

카카오 소셜로그인을 구현하기 위해서는 우선적으로 카카오 개발자 페이지에 회원가입 후 안드로이드 스튜디오에서 개발중인 내 애플리케이션을 추가해야 한다.

https://developers.kakao.com/

 

Kakao Developers

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

developers.kakao.com

추가하면 네이티브앱 키를 얻을 수가 있다. 얻은 네이티브 앱 키를 strings.xml에 정의해준다.

<string name="kakao_app_key">네이티브앱 키</string>

작성 후 아래 화면처럼 카카오 로그인 활성화 버튼을 눌러준다.

그 다음 모듈을 설정해 주어야 한다. 모듈 설정 관련해서는 카카오 개발자 페이지를 보고 그대로 작성하였다.

maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }

이 코드를 setting.gradle 파일에 추가해 주었다. 참고로 이 코드는 gradle을 설정하는 것으로 이 설정을 통해서  android.sdk를 간편하게 연동할 수 있다. 

그 다음 모듈을 설정해준다.

implementation "com.kakao.sdk:v2-all:2.12.0" // 전체 모듈 설치, 2.11.0 버전부터 지원
implementation "com.kakao.sdk:v2-user:2.12.0" // 카카오 로그인
implementation "com.kakao.sdk:v2-talk:2.12.0" // 친구, 메시지(카카오톡)
implementation "com.kakao.sdk:v2-story:2.12.0" // 카카오스토리
implementation "com.kakao.sdk:v2-share:2.12.0" // 메시지(카카오톡 공유)
implementation "com.kakao.sdk:v2-navi:2.12.0" // 카카오내비
implementation "com.kakao.sdk:v2-friend:2.12.0" // 카카오톡 소셜 피커, 리소스 번들 파일 포함

자신이 필요한 모듈만 추가해 주어도 된다. 

 

<안드로이드 플랫폼 등록>

안드로이드 플랫폼 등록을 해야하는데 이를 위해선 패키지명과 해시키가 필요하다. 

해시키를 얻기 위해선 터미널로 구하는 방법과 코드를 작성해서 구하는 방법이 있는데 나는 코드를 작성하여 구현해 보았다. 

*참고*

해시키: 안드로이드 개발 환경에서 가지고 있는 인증서에 대한 해쉬값이다. 즉 고유한 키값이다. 이를 등록하여 카카오 로그인 api를 앱에서 호출할 수 있다. 

디버그 해쉬키-> 앱 개발시 디버그 용으로 사용한다.

릴리즈 해쉬키 -> 앱 개발 완료 후 apk 파일 추출시 사용한다

이때 디버그 apk, 릴리즈 apk 모두 릴리즈 해쉬키를 사용해야한다. 

 

해시키를 얻기 위해서 MainActivity에서 코드작성을 통해 구했다.

@RequiresApi(Build.VERSION_CODES.P)

override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    binding= ActivityMainBinding.inflate(layoutInflater)

    setContentView(binding.root)

    try{

        val information =

            packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES)

        val signatures = information.signingInfo.apkContentsSigners

        val md = MessageDigest.getInstance("SHA")

        for(signature in signatures){

            val md : MessageDigest

            md = MessageDigest.getInstance("SHA")

            md.update(signature.toByteArray())

            var hashcode = String(Base64.encode(md.digest(),0))

            Log.d("hashcode", ""+hashcode)

        }



    }catch(e:Exception){

        Log.d("hashcode", "에러::" + e.toString())

    }

그다음 build하여 logcat창에서 찍힌 hashcode값을 확인후 입력해준다. 

 

<첫 로그인시 유저에게 받을 항목 설정>

다음으로 유저에게 동의받을 항목을 설정해 준다. 

 

<GlobalApplication 생성>

kakao SDK를 사용하기 위해서 네이티브 앱키를 초기화 해야한다. 

class GlobalApplication: Application()
{
    override fun onCreate(){
        super.onCreate()
        KakaoSdk.init(this, getString(R.string.kakao_app_key))
    }
}

그리고 GlobalApplication을 앱이 시작할때 다른 컴포넌트들 보다 먼저 실행되도록 Manifest 파일에서 설정한다.

(아래 코드 참조)

<application
    android:name=".GlobalApplication"

 

<Redirect URL 설정>

그다음 카카오 개발자 페이지를 참고하여 Redirect URL을 설정한다. 이는 카카오 인증서버가 지정된 Redirect URL로 인가코드를 보내면 Android SDK가 인가코드를 받아 토큰 받기를 요청한다.

<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네이티브앱키" />
    </intent-filter>
</activity>

<카카오 로그인 구현>

내가 개발하고 있는 어플상에서는 로그인 페이지가 앱을 시작하자마자 뜨게하고 그다음 MainActivity가 호출될 수 있도록 계획하였다.

1. FirstActivity를 생성하고 앱 시작시 제일 먼저뜨게 해줘야 한다. 이를 위해 Manifest파일에서 intent-filter 부분을 MainActivity 부분에 쓰여져 있던걸 잘라내 FirstActivity 부분에 붙여 넣어준다. 이때 android:exported를 false로 설정해서는 안된다. 인텐트 필터가 없는 경우 android:exported 기본값은 false여야한다. true로 설정시 의미는 다른 앱에서 해당 액티비티가 시작가능하다는 의미이다.

 

2. 로그인 구현 코드를 작성해준다.

(activity_first)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FirstActivity"
    android:orientation="vertical">



    <ImageButton
        android:id="@+id/kakaologin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/kakao_login_large_wide"
        />



</LinearLayout>

(FirstActivity)

class FirstActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        //로그인 정보 확인
        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))
                finish()
            }
        }
        val callback: (OAuthToken?, Throwable?) -> Unit={
            token,error->
            if(error != null){
                when{
                    error.toString()== AccessDenied.toString()->
                    {
                        Toast.makeText(this, "접근이 거부됨", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == InvalidClient.toString() -> {
                        Toast.makeText(this, "유효하지 않은 앱", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == InvalidGrant.toString() -> {
                        Toast.makeText(this,"인증수단이 유효하지 않아 인증할 수 없는 상태", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == InvalidRequest.toString() -> {
                        Toast.makeText(this, "요청 파라미터 오류", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == InvalidScope.toString() -> {
                        Toast.makeText(this, "유효하지 않은 scope ID", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == Misconfigured.toString() -> {
                        Toast.makeText(this, "설정이 올바르지 않음", Toast.LENGTH_SHORT).show()
                    }
                    error.toString() == ServerError.toString() -> {
                        Toast.makeText(this, "서버 내부 에러", Toast.LENGTH_SHORT).show()

                    }
                    error.toString() == Unauthorized.toString() -> {
                        Toast.makeText(this, "앱이 요청 권한이 없음", Toast.LENGTH_SHORT).show()
                    }
                    else->{
                        Toast.makeText(this, "기타 에러", Toast.LENGTH_SHORT).show()
                    }
                }
            }
            else if(token != null){
                Toast.makeText(this, "로그인에 성공하였습니다", Toast.LENGTH_SHORT).show()
                val intent = Intent(this, MainActivity::class.java)
                startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP))
                finish()
            }
        }
        val kakao_login_button = findViewById<ImageButton>(R.id.kakaologin)
        kakao_login_button.setOnClickListener{
            if(UserApiClient.instance.isKakaoTalkLoginAvailable(this)){
                UserApiClient.instance.loginWithKakaoTalk(this, callback = callback)
            }
            else{
                UserApiClient.instance.loginWithKakaoAccount(this, callback = callback)
            }
        }

    }
}

(구현 결과)

<회원탈퇴, 로그아웃 구현>

나의 계획은 HomeFragment내 로그아웃 버튼을 두고 그 버튼 클릭시 로그아웃 및 회원 탈퇴가 구현되어 있는 SecondActivity로 넘어가도록 해줄 것이다. 

아래 코드를 통해 HomeFragment -> SecondActivity로 넘어가도록 해준다.

(HomeFragment)

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    
    val view = inflater.inflate(R.layout.fragment_home,container,false)
    val outBtn = view.findViewById<ImageButton>(R.id.outBtn)
    outBtn.setOnClickListener{
        val intent = Intent(getActivity(), SecondActivity::class.java)
        startActivity(intent)
    }
    return view
}

그 다음 SecondActivity를 구현해준다.

 

(activity_second)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondActivity">
    <Button
        android:id="@+id/kakao_logout_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="로그아웃"
        android:textSize="20sp"
        app:layout_constraintBottom_toTopOf="@+id/kakao_unlink_button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

    <Button
        android:id="@+id/kakao_unlink_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="회원 탈퇴"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

(SecondActivity)

class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        //로그아웃
        val kakao_logout_button=findViewById<Button>(R.id.kakao_logout_button)

        kakao_logout_button.setOnClickListener {
            UserApiClient.instance.logout{error->
                //에러가 발생했다
                if(error != null){
                    Toast.makeText(this, "로그아웃실패 $error", Toast.LENGTH_SHORT).show()
                }else{
                    Toast.makeText(this, "로그아웃성공", Toast.LENGTH_SHORT).show()
                }
                val intent = Intent(this, FirstActivity::class.java)
                startActivity(intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP))
                finish()
            }
        }
        //회원탈퇴
        val kakao_unlink_button=findViewById<Button>(R.id.kakao_unlink_button)
        kakao_unlink_button.setOnClickListener {
            UserApiClient.instance.unlink{
                error->
                //에러가 있다
                if(error !=null){
                    Toast.makeText(this, "회원탈퇴 실패 $error", Toast.LENGTH_SHORT).show()

                }else{
                    Toast.makeText(this,"회원탈퇴 성공", Toast.LENGTH_SHORT).show()
                    val intent = Intent(this, FirstActivity::class.java)
                    startActivity(intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP))
                    finish()
                }
            }
        }
    }
}

 

'Frontend > Kotlin' 카테고리의 다른 글

Unit 7-1 강의정리  (1) 2023.01.21
Unit 6  (0) 2023.01.18
Unit 5  (0) 2023.01.10
RecyclerView구현  (0) 2022.12.09
Unit 3 solution  (0) 2022.11.20