본문 바로가기

Android/개념 및 예제

[Android] 카카오 로그인을 안드로이드 앱에 연동하기

이번에는 카카오 로그인을 연동하려고 합니다.

카카오 로그인을 실서비스에 적용하려고 하려고 할 때 필요한 작업은 다음 세 가지가 있습니다.

1. 프로젝트 설정

2. 기능 구현

3. 릴리즈 해시 키 설정 

 

이번 포스팅에서는 1,2번만 다루고 3번은 develop-writing.tistory.com/33 포스팅을 확인하시면 됩니다.

 

1. 프로젝트 설정

1-1 App key 확인

카카오 연동을 하기 위해서는 App Key를 발급받아 프로젝트에 등록하면 됩니다.

App Key는 카카오 개발자 홈페이지에서 받을 수 있습니다.

 

https://developers.kakao.com/

 

Kakao Developers

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

developers.kakao.com

 

내 애플리케이션을 만들었으면 다음 텝에서 앱 키를 확인할 수 있습니다.

저희는 앱 키 중에서 네이티브 앱 키를 사용할 것입니다.

 

 

1-2 안드로이드 플랫폼 등록

다음으로 앱의 패키지 명과 해시 키를 등록해야 합니다.

해시 키는 디버그와 릴리즈 해시키로 2개가 있는데 지금은 디버그 해시 키를 등록할 것입니다.

만약 서비스 중인 앱이라면 릴리즈 해시 키를 무조건 등록해야 합니다.

릴리즈 해시키를 구하는 방법은 추후에 다루겠습니다.

지금은 디버그 해시 키를 등록하겠습니다.

 

디버그 해시키는 앱에서 다음 코드를 통해서 구할 수 있습니다.

private void getHashKey(){
        PackageInfo packageInfo = null;
        try {
            packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        if (packageInfo == null)
            Log.e("KeyHash", "KeyHash:null");

        for (Signature signature : packageInfo.signatures) {
            try {
                MessageDigest md = MessageDigest.getInstance("SHA");
                md.update(signature.toByteArray());
                Log.d("KeyHash", Base64.encodeToString(md.digest(), Base64.DEFAULT));
            } catch (NoSuchAlgorithmException e) {
                Log.e("KeyHash", "Unable to get MessageDigest. signature=" + signature, e);
            }
        }
 }

 

이렇게 구한 해시 키와 패키지 명을 가지고 '플랫폼' 탭에서 안드로이드 플랫폼 등록을 하시면 됩니다.

 

1-3 카카오 로그인 활성화

다음으로 내 앱에 카카오 로그인 API를 활성화하고, 카카오 로그인할 때 사용자 정보 중 가져올 정보를 선택합니다.

 

2. 기능 구현

프로젝트에 Kakao SDK 추가

strings.xml

  <resources>
      <string name="kakao_app_key">앱 키</string>
  </resources>

 

build.gradle(Project:~)

 

  allprojects {
      repositories {
          google()
          jcenter()
          maven{
              url "https://maven.google.com"
          }
          /*카카오*/
          maven { url 'http://devrepo.kakao.com:8088/nexus/content/groups/public/'}
      }
  }

 

gradle.properties파일에도 다음과 같이 두줄을 추가해줍니다.

gradle.properties

  KAKAO_SDK_GROUP=com.kakao.sdk
  KAKAO_SDK_VERSION=1.27.0

 

build.gradle(Module:app)에 카카오톡 SDK를 추가해줍니다.

build.gradle(Module:app)

 

  dependencies {
      implementation group: project.KAKAO_SDK_GROUP, name: 'usermgmt', version: project.KAKAO_SDK_VERSION
  }

 

Kakao SDK를 사용하기 위해서는 초기화를 해줘야 합니다. 초기화는 GlobalApplication 공유 클래스를 만들어 앱 수준에서 관리하도록 하겠습니다. kakaoSDKAdapter클래스는 KakaoAdapter를 상속받습니다.

 

GlobalApplication.java

public class GlobalApplication extends Application {
    private static GlobalApplication instance;

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;

        // Kakao Sdk 초기화
        KakaoSDK.init(new KakaoSDKAdapter());
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        instance = null;
    }

    public static Application getInstance(){
        if (instance == null){
            throw new IllegalStateException("this app illegal state");
        }
        return instance;
    }

    public class KakaoSDKAdapter extends KakaoAdapter {

        @Override
        public ISessionConfig getSessionConfig() {
            return new ISessionConfig() {
                @Override
                public AuthType[] getAuthTypes() {
                    // Kakao SDK로그인을 하는 방식에 대한 Enum class (카카오톡 앱 + 카카오 스토리 + 웹뷰 다이어로그 포함)
                    return new AuthType[] {AuthType.KAKAO_LOGIN_ALL};
                }

                @Override
                public boolean isUsingWebviewTimer() {
                    return false;
                }

                @Override
                public boolean isSecureMode() {
                    return false;
                }

                @Nullable
                @Override
                public ApprovalType getApprovalType() {
                    return ApprovalType.INDIVIDUAL;

                }

                @Override
                public boolean isSaveFormData() {
                    return true;
                }
            };
        }

        @Override
        public IApplicationConfig getApplicationConfig() {
            return GlobalApplication::getInstance;
        }
    }
}

 

AuthType

Kakao SDK로그인을 하는 방식에 대한 Enum class

 

KAKAO_TALK 0 kakaotalk으로 login을 하고 싶을때 지정.
KAKAO_STORY 1 kakaostory으로 login을 하고 싶을때 지정.
KAKAO_ACCOUNT 2 웹뷰 Dialog를 통해 카카오 계정연결을 제공하고 싶을경우 지정.
KAKAO_TALK_EXCLUDE_NATIVE_LOGIN 3 카카오톡으로만 로그인을 유도하고 싶으면서 계정이 없을때 계정생성을 위한 버튼도 같이 제공을 하고 싶다면 지정.
KAKAO_TALK과 중복 지정불가.
KAKAO_LOGIN_ALL 4 모든 로그인 방식을 사용하고 싶을 때 지정.

 

manifests.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.lakue.kakaologinsample">
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:name=".GlobalApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <meta-data
            android:name="com.kakao.sdk.AppKey"
            android:value="@string/kakao_app_key" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

인터넷을 사용하기 위해 <uses-permission android:name="android.permission.INTERNET" /> 를 넣어줘야 하고,

카카오에서 받아온 앱 키를 설정할 메타데이터를 넣어줘야 합니다.

 

activity_main.xml

로그인 화면은 로그인, 로그아웃 버튼으로 이루어져 있습니다.

<?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=".MainActivity">


    <Button
        android:id="@+id/loginV1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="328dp"
        android:text="카카오 로그인 v1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.734" />

    <Button
        android:id="@+id/logout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="로그아웃"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.636" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

 

MainActivity.java

로그인 버튼을 클릭하면 로그인 요청,

로그아웃 클릭 시 로그아웃 요청 및 사용자의 세션을 제거하도록 했습니다.

public class MainActivity extends AppCompatActivity {
    private Button loginV1, logout;
    private SessionCallback sessionCallback = new SessionCallback();
    Session session;
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        loginV1 = findViewById(R.id.loginV1);
        logout = findViewById(R.id.logout);


        session = Session.getCurrentSession();
        session.addCallback(sessionCallback);

        loginV1.setOnClickListener(v -> {
            if (Session.getCurrentSession().checkAndImplicitOpen()) {
                Log.d(TAG, "onClick: 로그인 세션살아있음");
                // 카카오 로그인 시도 (창이 안뜬다.)
                sessionCallback.requestMe();
            } else {
                Log.d(TAG, "onClick: 로그인 세션끝남");
                // 카카오 로그인 시도 (창이 뜬다.)
                session.open(AuthType.KAKAO_LOGIN_ALL, MainActivity.this);
            }
        });

        logout.setOnClickListener(v -> {
            Log.d(TAG, "onCreate:click ");
            UserManagement.getInstance()
                    .requestLogout(new LogoutResponseCallback() {
                        @Override
                        public void onSessionClosed(ErrorResult errorResult) {
                            super.onSessionClosed(errorResult);
                            Log.d(TAG, "onSessionClosed: "+errorResult.getErrorMessage());

                        }
                        @Override
                        public void onCompleteLogout() {
                            if (sessionCallback != null) {
                                Session.getCurrentSession().removeCallback(sessionCallback);
                            }
                            Log.d(TAG, "onCompleteLogout:logout ");
                        }
                    });
        });

        // 카카오 개발자 홈페이지에 등록할 해시키 구하기
//        getHashKey();
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();

        // 세션 콜백 삭제
        Session.getCurrentSession().removeCallback(sessionCallback);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        // 카카오톡|스토리 간편로그인 실행 결과를 받아서 SDK로 전달
        if (Session.getCurrentSession().handleActivityResult(requestCode, resultCode, data)) {
            return;
        }

        super.onActivityResult(requestCode, resultCode, data);
    }
}

 

 

SessionCallback.java

 이제 로그인 결과 값을 전달받기 위한 Callback클래스를 구현하겠습니다.

public class SessionCallback implements ISessionCallback {

    // 로그인에 성공한 상태
    @Override
    public void onSessionOpened() {
        requestMe();
    }

    // 로그인에 실패한 상태
    @Override
    public void onSessionOpenFailed(KakaoException exception) {
        Log.e("SessionCallback :: ", "onSessionOpenFailed : " + exception.getMessage());
    }

    // 사용자 정보 요청
    public void requestMe() {
        UserManagement.getInstance()
                .me(new MeV2ResponseCallback() {
                    @Override
                    public void onSessionClosed(ErrorResult errorResult) {
                        Log.e("KAKAO_API", "세션이 닫혀 있음: " + errorResult);
                    }

                    @Override
                    public void onFailure(ErrorResult errorResult) {
                        Log.e("KAKAO_API", "사용자 정보 요청 실패: " + errorResult);
                    }

                    @Override
                    public void onSuccess(MeV2Response result) {
                        Log.i("KAKAO_API", "사용자 아이디: " + result.getId());
                        String id = String.valueOf(result.getId());
                        UserAccount kakaoAccount = result.getKakaoAccount();
                        if (kakaoAccount != null) {

                            // 이메일
                            String email = kakaoAccount.getEmail();
                            Profile profile = kakaoAccount.getProfile();
                            if (profile ==null){
                                Log.d("KAKAO_API", "onSuccess:profile null ");
                            }else{
                                Log.d("KAKAO_API", "onSuccess:getProfileImageUrl "+profile.getProfileImageUrl());
                                Log.d("KAKAO_API", "onSuccess:getNickname "+profile.getNickname());
                            }
                            if (email != null) {

                                Log.d("KAKAO_API", "onSuccess:email "+email);
                            } 

                            // 프로필
                            Profile _profile = kakaoAccount.getProfile();

                            if (_profile != null) {

                                Log.d("KAKAO_API", "nickname: " + _profile.getNickname());
                                Log.d("KAKAO_API", "profile image: " + _profile.getProfileImageUrl());
                                Log.d("KAKAO_API", "thumbnail image: " + _profile.getThumbnailImageUrl());

                            } else if (kakaoAccount.profileNeedsAgreement() == OptionalBoolean.TRUE) {
                                // 동의 요청 후 프로필 정보 획득 가능

                            } else {
                                // 프로필 획득 불가
                            }
                        }else{
                            Log.i("KAKAO_API", "onSuccess: kakaoAccount null");
                        }
                    }
                });

    }
}

 

이대로 앱을 실행하면 카카오 로그인이 정상적으로 됩니다.

로그인 버튼 클릭 시 카카로 로그인 웹페이지에 해당하는 웹뷰가 나타납니다.

카카오 계정으로 로그인 하면 앱에서 설정했던 정보 이용에 대한 동의를 받습니다.

 

다음까지 마치면 앱에서 사용자에 대한 정보를 받은 것을 확인할 수 있습니다.

마찬가지로 로그아웃까지 처리 가능합니다.

 

전체 소스코드는 깃허브에서 확인할 수 있습니다.

github.com/keepseung/Android-Blog-Source

 

keepseung/Android-Blog-Source

https://develop-writing.tistory.com/ 에서 제공하는 예제. Contribute to keepseung/Android-Blog-Source development by creating an account on GitHub.

github.com

 

** 자동 로그인 구현 시

앱이 시작했을 때 매번 위처럼 로그인하는 것은 불편합니다.

카카오 계정으로 자동로그인을 구현하려면 사용자의 세션이 유효한지 확인해야 합니다.

다음 코드를 통해서 앱 시작시 사용자 세션을 확인할 수 있습니다.

if (Session.getCurrentSession().checkAndImplicitOpen()) {
	 // 사용자의 세션이 유효하므로 카카오 사용자 정보를 요청한다.
     UserManagement.getInstance()
     .me(new MeV2ResponseCallback() {
     ''''''
}

 

 

 

** 인터넷 권한을 빼먹은 경우

다음과 같이 웹뷰에서 카카오 로그인 페이지를 로드할 수 없다는 에러를 받습니다.