읽기 전

  • 불필요한 코드나 잘못 작성된 내용에 대한 지적은 언제나 환영합니다.
  • 개인적으로 사용해보면서 배운 점을 정리한 글입니다.
  • Kakao, Naver에서 이어지는 포스팅으로 중복된 내용은 제거하였습니다. 마지막 전체 코드 항목에서 확인하거나 이전 포스팅에서 출발해주세요
  • 전체 프로젝트 파일 링크 : MySNSAccount Project

관련 포스팅

Android에서 Google 계정 연동하기

Android 사이드 프로젝트에 유저 계정관리를 로컬이 아닌 SNS 계정으로 사용하자는 의견이 있어 Kakao, Naver에서 출발하여 마지막으로 모두가 많이 사용하고 제가 제일 애정하는 google 계정을 third party 앱에 연동하는 방법을 정리하려 한다.

Firebase에 Android 프로젝트 등록

기본적으로 구글 로그인은 Firebase와 함께 동작한다. Firebase Console이 다른 업체의 로그인 관련 개발자 센터라고 보면 된다.

Firebase Console 링크

새로운 프로젝트를 추가하자. 프로젝트 이름, Google 애널리틱스 설정을 모두 마치면 Firebase 프로젝트가 생성되면서 새로운 앱을 추가할 수 있다.

Android_SNS_Auth_003-01

Android_SNS_Auth_003-02

Firebase에 앱 등록

Android 프로젝트이므로 Android를 선택하면 사진과 같이 앱 등록 과정을 진행한다.

Android_SNS_Auth_003-03

항상 그래왔듯이 AndroidManifest.xml에서 패키지명을 참고하고 프로젝트의 별칭을 넣는다. 그리고 디버그 서머여 인증서 SHA-1을 요구한다. 해당 키는 Android Studio의 기본 기능으로 지원하고 있다. 가장 우측 [Gradle] 버튼 클릭 후 팝업되는 트리를 따라 [app] - [Tasks] - [android] - [SigningResport]를 더블클릭하면 특정 메소드가 실행되면서 로그에 해시값들이 출력된다.

Android_SNS_Auth_003-04

Android_SNS_Auth_003-05

구글이 SHA-1 값을 요구하고 있으므로 복사해서 붙여준다.

로그인 방법 선택

앱 등록을 끝마쳤지만 어떤 방식으로 로그인을 진행할 것인지 Firebase에 설정해둬야 한다. Firebase는 구글뿐만 아니라 다양한 방식의 로그인을 지원하고 있다. 구글 로그인 방식만을 지원할 예정으로 해당 루트만 열어주었다.

Android_SNS_Auth_003-06

Android에 Firebase, 구글 인증 Module import

안내하는 대로 json파일을 다운받아 app 폴더에 넣는다. 그리고 gradle을 아래와 같이 수정한다.

build.gradle(Project)

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.0.1"
        // add below line
        classpath 'com.google.gms:google-services:4.3.4'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

build.gradle(Module)

상단에 plugin을 추가한다.

apply plugin: 'com.google.gms.google-services'

하단에 Module을 추가한다. BOM을 추가하면 Firebase 하위 모듈들의 버전을 명세할 필요가 없다. 그리고 Firebase-auth와 Google play service 라이브러리를 추가한다.

dependencies {
    implementation platform('com.google.firebase:firebase-bom:26.0.0')
    implementation 'com.google.firebase:firebase-analytics'
    implementation 'com.google.firebase:firebase-auth'
    implementation 'com.google.android.gms:play-services-auth:18.1.0'
}

json 파일을 app폴더에 넣고 위와 같이 gradle 파일을 수정한 뒤 Sync를 하면 프로젝트에 적용된다.

Android 구글 로그인 구조

Android_SNS_Auth_003-07

로그인 버튼 및 자동 로그인 구현

로그인 레이아웃 정리

이번엔 기존 버튼과 커스텀 버튼을 연결했던 다른 로그인과 달리 구글 로그인 버튼에는 따로 리스너가 없기 떄문에 특정 메소드를 호출시켜 인증절차를 진행해야 한다.

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">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/hg_main_70"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.70" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/hg_main_95"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.95" />

    <LinearLayout
        android:id="@+id/layout_login_btn_group"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toTopOf="@+id/hg_main_95"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/hg_main_70">

        <Button
            android:id="@+id/btn_kakao_login"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="6dp"
            android:layout_weight="1"
            android:text="카카오 로그인" />

        <Button
            android:id="@+id/btn_naver_login"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="6dp"
            android:layout_weight="1"
            android:text="네이버 로그인" />

        <Button
            android:id="@+id/btn_google_login"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="6dp"
            android:layout_weight="1"
            android:text="구글 로그인" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:visibility="gone"
        app:layout_constraintTop_toTopOf="parent"
        tools:visibility="gone">

        <com.kakao.usermgmt.LoginButton
            android:id="@+id/btn_kakao_login_basic"
            android:layout_width="0dp"
            android:layout_height="0dp" />

        <com.nhn.android.naverlogin.ui.view.OAuthLoginButton
            android:id="@+id/btn_naver_login_basic"
            android:layout_width="0dp"
            android:layout_height="0dp" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

로그인 인스턴스 획득

구글 또한 네이버처럼 로그인 동작 전 인스턴스를 받아와야 한다. 싱글톤 패턴처럼 인스턴스를 획득한 뒤 시작한다.

MainActivity - 구글 로그인 instance 획득

mGoogleLoginModule = FirebaseAuth.getInstance();

로그인 버튼 정의 및 핸들러 연결

버튼을 정의하고 버튼 클릭 시 동작할 행위를 핸들러로 정의하고서 연결해준다.

MainActivity - 로그인 버튼 정의

mGoogleLoginBtn = findViewById(R.id.btn_google_login);
mGoogleLoginBtn.setOnClickListener(mGoogleLoginListener);

MainActivity - 구글 로그인 버튼 클릭 시 핸들러 정의

    Button.OnClickListener mGoogleLoginListener = new ImageView.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this, GoogleLogin.class);
            startActivity(intent);
            finish();
        }
    };

로그인 버튼을 클릭하면 구글 로그인을 담당할 GoogleLogin.class로 보낸다.

세션 체크 및 자동 로그인

인스턴스를 받았던 FirebaseAuth 객체로부터 현재 계정정보가 존재하는지 체크하고 없으면 로그인 버튼 클릭을 대기하고 있으면 곧바로 GoogleLogin.class로 보내 로그인 절차를 수행한다.

MainActivity - 구글 세션 체크

    private boolean HasGoogleSession() {
        if (mGoogleLoginModule.getCurrentUser() == null) {
            return false;
        }
        return true;
    }

MainActivity - 로그인 로직

        if (!HasGoogleSession()) {
            // 다른 유형의 로그인 로직 부분 생략
        } else if (HasGoogleSession()) {
            Intent intent = new Intent(MainActivity.this, GoogleLogin.class);
            startActivity(intent);
            finish();
        }
    }

로그인 동작

구글 로그인은 자체 내장된 메소드들을 사용하여 로그인 절차를 수행하는 듯하다. 따라서 코드만 보면 조금 복잡할 수 있는데 가이드에 기능 별로 잘 나와있으니 요구 기능에 맞춰서 붙이기만 하면 제대로 작동한다. 먼저 FirebaseAuth 객체를 통해 instance를 가져온 뒤 구글 로그인 옵션을 설정한다. DEFAULT_SIGN_IN 옵션을 주지 않으면 현재 기기가 인식하는 구글 계정을 통해 곧바로 로그인된다. 하지만 기기, 브라우저가 여러 계정을 들고 있는 경우도 있다. 진입 시 다른 계정을 선택할 수 있는 옵션을 부여하는 값이 DEFAULT_SIGN_IN 이다. default_web_client_id는 앞서 play-service 모듈을 성공적으로 추가했다면 자동으로 등록되어 있다. 마지막으로 GoogleSignIn 객체에 context와 정의했던 로그인 옵션 객체를 넣으면 된다.

GoogleLogin.class - 구글 로그인 시도

        mGoogleLoginModule = FirebaseAuth.getInstance();
        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();
        mGoogleClient = GoogleSignIn.getClient(getApplicationContext(), gso);

로그인 시도를 하고서 유저가 존재하면 id 식별값, 닉네임을 저장하고 다음 액티비티로 넘어간다. 만약 유저가 존재하지 않는다면 자체 구글 로그인 화면으로 넘어가 계정 정보를 입력하게 한다.

GoogleLogin.class 사용자 체크 및 계정 요청

        if (mGoogleLoginModule.getCurrentUser() == null) {
            Intent signInIntent = mGoogleClient.getSignInIntent();
            startActivityForResult(signInIntent, RC_SIGN_IN);

        } else if (mGoogleLoginModule.getCurrentUser() != null) {
            List<String> userInfo = new ArrayList<>();
            GlobalHelper mGlobalHelper = (GlobalHelper) getApplication();
            userInfo.add(String.format("%s-%s", "GOOGLE", mGoogleLoginModule.getCurrentUser().getUid()));
            userInfo.add(mGoogleLoginModule.getCurrentUser().getDisplayName());
            GlobalHelper.setGlobalUserLoginInfo(userInfo);
            Intent intent = new Intent(GoogleLogin.this, SecondActivity.class);
            startActivity(intent);
            finish();
        }

계정 정보가 없어 사용자로부터 정보를 받게하는 startActivityForResult 메소드가 끝나면 호출될 메소드를 정의한다. onActivityResult 메소드에 RC_SIGN_IN(9001) 값을 받아 성공했음이 확인되면 받아온 결과로 인증 절차를 계속한다. 만약 실패하면 다시 MainActivity로 돌려 보낸다. 그리고 계정정보가 없어 사용자로부터 새롭게 계정정보를 받아냈으니 그 정보를 Firebase에 보내는 메소드를 정의하고 그 과정까지 모두 끝내면 id 식별값과 닉네임을 전역변수에 저장하고 다음 액티비티로 넘어간다.

GoogleLogin.class - 사용자로부터 계정정보를 받은 뒤 동작

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RC_SIGN_IN) {
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            if (result.isSuccess()) {
                GoogleSignInAccount account = result.getSignInAccount();
                firebaseAuthWithGoogle(account);
            } else {
                Toast.makeText(getApplicationContext(), "Google Sign In Failed", Toast.LENGTH_LONG).show();
                Intent intent = new Intent(GoogleLogin.this, MainActivity.class);
                startActivity(intent);
                finish();
            }
        }
    }

GoogleLogin.class - 받은 계정정보를 Firebase에 전송, 정보 저장

    private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {
        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        mGoogleLoginModule.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        if (task.isSuccessful()) {
                            if (mGoogleLoginModule.getCurrentUser() != null) {
                                List<String> userInfo = new ArrayList<>();
                                GlobalHelper mGlobalHelper = (GlobalHelper) getApplication();
                                userInfo.add(mGoogleLoginModule.getCurrentUser().getUid());
                                userInfo.add(mGoogleLoginModule.getCurrentUser().getDisplayName());
                                GlobalHelper.setGlobalUserLoginInfo(userInfo);
                                Intent intent = new Intent(GoogleLogin.this, SecondActivity.class);
                                startActivity(intent);
                                finish();
                            }
                        } else {
                            Toast.makeText(getApplicationContext(), "Google Authentication Failed", Toast.LENGTH_LONG).show();
                        }
                    }
                });
    }

구글 로그아웃 / 앱 연동 해제 (탈퇴하기)

SecondActivity는 이전 포스팅에서 다뤘으므로 곧바로 로그아웃. 탈퇴 기능을 정리하였다.

구글 로그아웃

구글의 경우 자체 인증 방식과 Firebase를 같이 사용하고 있으므로 signOut() 메소드를 두 번 호출해야 한다.

GoogleAuthHelper.class - 구글 로그아웃

    public static void accountLogout(Context context, SecondActivity activity) {
        if (FirebaseAuth.getInstance().getCurrentUser() != null) {
            FirebaseAuth.getInstance().signOut();
            GoogleSignIn.getClient(context, GoogleSignInOptions.DEFAULT_SIGN_IN).signOut();
            activity.directToMainActivity(true);
        }
    }

FirebaseAuth, GoogleSignIn 객체를 호출한 뒤 signOut() 메소드를 호출한다. GoogleSignIn에 옵션을 준 이유는 그렇지 않으면 재접속 시 남아있는 구글 유저 정보를 인식해 곧바로 재로그인하여 다른 계정으로의 전환을 막기 때문이다.

구글 연동 해제하기 (회원탈퇴)

위의 로그아웃 특징으로 인해 연동해제와 로그아웃의 경우 동작하는 모습이 겉에서 보기엔 동일하다는 문제점이 있다. 먼저 FirebaseAuth 객체를 불러 계정정보 삭제 요청을 한 뒤 종료된 시점에 성공 여부를 체크하여 동작을 결정한다. 이로써 Firebase 인증이 해제되었으며 로그아웃과 마찬가지로 GoogleSignIn 객체를 통해 revokeAccess() 메소드를 호출한다. 이번 옵션도 해주지 않으면 탈퇴했다는 느낌을 사용자에게 제공하기 어렵게 때문에 프로젝트에선 DEFAULT_SIGN_IN을 부여하였다.

GoogleLogin.class - 구글 연동 해제하기

    public static void accountResign(final Context context, final SecondActivity activity) {
        if (FirebaseAuth.getInstance().getCurrentUser() != null) {
            // 구글 연동 해제
            try {
                FirebaseAuth.getInstance().getCurrentUser().delete().addOnCompleteListener(new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        if (task.isSuccessful()) {
                            activity.directToMainActivity(true);
                        }
                        else {
                            activity.directToMainActivity(false);
                        }
                    }
                }); // Firebase 인증 해제
                GoogleSignIn.getClient(context, GoogleSignInOptions.DEFAULT_SIGN_IN).revokeAccess(); // Google 계정 해제
            } catch (Exception e) {
                activity.directToMainActivity(false);
            }
        }
    }

전체 코드

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"
    package="com.example.mysnsaccount">
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name=".GlobalHelper"
        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">
        <activity android:name=".SecondActivity" />
        <activity android:name=".KakaoLogin" />
        <activity android:name=".NaverLogin" />
        <activity android:name=".GoogleLogin" />

        <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>

GlobalHelper.class

package com.example.mysnsaccount;

import android.app.Application;
import android.content.Context;

import com.kakao.auth.ApprovalType;
import com.kakao.auth.AuthType;
import com.kakao.auth.IApplicationConfig;
import com.kakao.auth.ISessionConfig;
import com.kakao.auth.KakaoAdapter;
import com.kakao.auth.KakaoSDK;

import java.util.ArrayList;
import java.util.List;

public class GlobalHelper extends Application {
    private static volatile GlobalHelper mInstance = null;
    private static List<String> mGlobalUserLoginInfo = new ArrayList<>();

    public static GlobalHelper getGlobalApplicationContext() {
        if (mInstance == null) {
            throw new IllegalStateException("This Application does not GlobalAuthHelper");
        }
        return mInstance;
    }

    public static List<String> getGlobalUserLoginInfo() {
        return mGlobalUserLoginInfo;
    }

    public static void setGlobalUserLoginInfo(List<String> userLoginInfo) {
        mGlobalUserLoginInfo = userLoginInfo;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
        KakaoSDK.init(new KakaoSDKAdapter());
    }

    public class KakaoSDKAdapter extends KakaoAdapter {
        @Override
        public ISessionConfig getSessionConfig() {
            return new ISessionConfig() {
                @Override
                public AuthType[] getAuthTypes() {
                    return new AuthType[]{AuthType.KAKAO_LOGIN_ALL};
                }

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

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

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

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

        @Override
        public IApplicationConfig getApplicationConfig() {
            return new IApplicationConfig() {
                @Override
                public Context getApplicationContext() {
                    return GlobalHelper.getGlobalApplicationContext();
                }
            };
        }
    }
}

MainActivity

package com.example.mysnsaccount;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.google.firebase.auth.FirebaseAuth;
import com.kakao.auth.Session;
import com.kakao.usermgmt.LoginButton;
import com.example.mysnsaccount.KakaoLogin.KakaoSessionCallback;
import com.nhn.android.naverlogin.OAuthLogin;
import com.nhn.android.naverlogin.OAuthLoginHandler;
import com.nhn.android.naverlogin.data.OAuthLoginState;
import com.nhn.android.naverlogin.ui.view.OAuthLoginButton;

public class MainActivity extends AppCompatActivity {
    private Button mKakaoLoginBtn, mNaverLoginBtn, mGoogleLoginBtn;
    private LoginButton mKakaoLoginBtnBasic;
    private OAuthLoginButton mNaverLoginBtnBasic;
    private KakaoSessionCallback sessionCallback;
    private OAuthLogin mNaverLoginModule;
    private NaverLogin mNaverLoginAuth;
    final String NAVER_CLIENT_ID = "zQtbz1mqovnAUiFlo7RB";
    final String NAVER_CLIENT_SECRET = "fMosFMzLxn";
    private FirebaseAuth mGoogleLoginModule;

    @SuppressLint("HandlerLeak")
    private OAuthLoginHandler mNaverLoginHandler = new OAuthLoginHandler() {
        @Override
        public void run(boolean success) {
            if (success) {
                Intent intent = new Intent(MainActivity.this, NaverLogin.class);
                startActivity(intent);
                finish();
            } else {
                Toast.makeText(getApplicationContext(), "Naver Login Failed!", Toast.LENGTH_LONG);
            }
        }
    };

    Button.OnClickListener mGoogleLoginListener = new ImageView.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity.this, GoogleLogin.class);
            startActivity(intent);
            finish();
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mKakaoLoginBtn = findViewById(R.id.btn_kakao_login);
        mNaverLoginBtn = findViewById(R.id.btn_naver_login);
        mGoogleLoginBtn = findViewById(R.id.btn_google_login);
        mKakaoLoginBtnBasic = findViewById(R.id.btn_kakao_login_basic);
        mNaverLoginBtnBasic = findViewById(R.id.btn_naver_login_basic);
        mNaverLoginBtnBasic.setOAuthLoginHandler(mNaverLoginHandler);

        mKakaoLoginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mKakaoLoginBtnBasic.performClick();
            }
        });

        mNaverLoginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mNaverLoginBtnBasic.performClick();
            }
        });

        mGoogleLoginBtn.setOnClickListener(mGoogleLoginListener);

        mNaverLoginModule = OAuthLogin.getInstance();
        mNaverLoginModule.init(
                this
                , NAVER_CLIENT_ID
                , NAVER_CLIENT_SECRET
                , "네이버 아이디로 로그인"
        );

        mGoogleLoginModule = FirebaseAuth.getInstance();
        if (!HasKakaoSession() && !HasNaverSession() && !HasGoogleSession()) {
            sessionCallback = new KakaoSessionCallback(getApplicationContext(), MainActivity.this);
            Session.getCurrentSession().addCallback(sessionCallback);
        } else if (HasKakaoSession()) {
            sessionCallback = new KakaoSessionCallback(getApplicationContext(), MainActivity.this);
            Session.getCurrentSession().addCallback(sessionCallback);
            Session.getCurrentSession().checkAndImplicitOpen();
        } else if (HasNaverSession()) {
            Intent intent = new Intent(MainActivity.this, NaverLogin.class);
            startActivity(intent);
            finish();
        } else if (HasGoogleSession()) {
            Intent intent = new Intent(MainActivity.this, GoogleLogin.class);
            startActivity(intent);
            finish();
        }
    }

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

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

    private boolean HasKakaoSession() {
        if (!Session.getCurrentSession().checkAndImplicitOpen()) {
            return false;
        }
        return true;
    }

    private boolean HasNaverSession() {
        if (OAuthLoginState.NEED_LOGIN.equals(mNaverLoginModule.getState(getApplicationContext())) ||
                OAuthLoginState.NEED_INIT.equals(mNaverLoginModule.getState(getApplicationContext()))) {
            return false;
        }
        return true;
    }

    private boolean HasGoogleSession() {
        if (mGoogleLoginModule.getCurrentUser() == null) {
            return false;
        }
        return true;
    }

    public void directToSecondActivity(Boolean result) {
        Intent intent = new Intent(MainActivity.this, SecondActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        if (result) {
            Toast.makeText(getApplicationContext(), "로그인 성공!", Toast.LENGTH_SHORT).show();
            startActivity(intent);;
            finish();
        }
    }
}

KakaoLogin.class

package com.example.mysnsaccount;

import android.app.Activity;
import android.content.Context;
import android.widget.Toast;

import com.kakao.auth.ISessionCallback;
import com.kakao.network.ErrorResult;
import com.kakao.usermgmt.UserManagement;
import com.kakao.usermgmt.callback.MeV2ResponseCallback;
import com.kakao.usermgmt.response.MeV2Response;
import com.kakao.util.exception.KakaoException;

import java.util.ArrayList;
import java.util.List;

public class KakaoLogin extends Activity {
    public static class KakaoSessionCallback implements ISessionCallback {
        private Context mContext;
        private MainActivity mainActivity;

        public KakaoSessionCallback(Context context, MainActivity activity) {
            this.mContext = context;
            this.mainActivity = activity;
        }

        @Override
        public void onSessionOpened() {
            requestMe();
        }

        @Override
        public void onSessionOpenFailed(KakaoException e) {
            Toast.makeText(mContext, "KaKao 로그인 오류가 발생했습니다. " + e.toString(), Toast.LENGTH_SHORT).show();
        }

        protected void requestMe() {
            UserManagement.getInstance().me(new MeV2ResponseCallback() {
                @Override
                public void onSessionClosed(ErrorResult errorResult) {
                    mainActivity.directToSecondActivity(false);
                }

                @Override
                public void onSuccess(MeV2Response result) {
                    List<String> userInfo = new ArrayList<>();
                    userInfo.add(String.valueOf(result.getId()));
                    userInfo.add(result.getKakaoAccount().getProfile().getNickname());
                    GlobalHelper mGlobalHelper = new GlobalHelper();
                    mGlobalHelper.setGlobalUserLoginInfo(userInfo);

                    mainActivity.directToSecondActivity(true);
                }
            });
        }
    }
}

NaverLogin.class

package com.example.mysnsaccount;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;

import com.nhn.android.naverlogin.OAuthLogin;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class NaverLogin extends Activity {
    final String NAVER_CLIENT_ID = "zQtbz1mqovnAUiFlo7RB";
    final String NAVER_CLIENT_SECRET = "fMosFMzLxn";
    final String NAVER_RESPONSE_CODE = "00"; // 정상 반환 시 코드
    final String[] NAVER_JSON_KEY = {"id", "nickname"};

   @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        OAuthLogin mNaverLoginModule = OAuthLogin.getInstance();

        String accessToken = mNaverLoginModule.getAccessToken(getApplicationContext());
        if (accessToken != null && OAuthLoginState.OK.equals(mNaverLoginModule.getState(getApplicationContext()))) {
            ReqNHNUserInfo reqNaverUserInfo = new ReqNHNUserInfo();
            reqNaverUserInfo.execute(accessToken);
        } else {
            RefreshNHNToken tokenRefresh = new RefreshNHNToken();
            try {
                tokenRefresh.execute().get();
            } catch (Exception e) {
                e.printStackTrace();
            }
            ReqNHNUserInfo reqNaverUserInfo = new ReqNHNUserInfo();
            reqNaverUserInfo.execute(mNaverLoginModule.getAccessToken(getApplicationContext()));
        }
    }

    class RefreshNHNToken extends AsyncTask<Void, Void, Boolean> {
        @Override
        protected Boolean doInBackground(Void... params) {
            try {
                OAuthLogin mNaverLoginModule = OAuthLogin.getInstance();
                mNaverLoginModule.refreshAccessToken(getApplicationContext());
            } catch (Exception e) {
                Log.e("Error RefreshNHNToken", e.toString());
            }
            return true;
        }
    }

    class ReqNHNUserInfo extends AsyncTask<String, Void, String> {
        String result;

        @Override
        protected String doInBackground(String... strings) {
            String token = strings[0];// 네이버 로그인 접근 토큰;
            String header = "Bearer " + token; // Bearer 다음에 공백 추가
            try {
                String apiURL = "https://openapi.naver.com/v1/nid/me";
                URL url = new URL(apiURL);
                HttpURLConnection con = (HttpURLConnection) url.openConnection();
                con.setRequestMethod("GET");
                con.setRequestProperty("Authorization", header);
                int responseCode = con.getResponseCode();
                BufferedReader br;
                if (responseCode == 200) { // 정상 호출
                    br = new BufferedReader(new InputStreamReader(con.getInputStream()));
                } else {  // 에러 발생
                    br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
                }
                String inputLine;
                StringBuilder response = new StringBuilder();

                while ((inputLine = br.readLine()) != null) {
                    response.append(inputLine);
                }
                result = response.toString();
                br.close();
                Log.d("ReqNHNUserInfo Response", result);
            } catch (Exception e) {
                Log.e("Error ReqNHNUserInfo", e.toString());
            }
            return result;
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            try {
                JSONObject object = new JSONObject(result);
                if (object.getString("resultcode").equals(NAVER_RESPONSE_CODE)) {
                    List<String> userInfo = new ArrayList<>();
                    JSONObject jsonObject = new JSONObject(object.getString("response"));
                    userInfo.add(String.format("%s-%s", "NAVER", jsonObject.getString(NAVER_JSON_KEY[0])));
                    userInfo.add(jsonObject.getString(NAVER_JSON_KEY[1]));
                    GlobalHelper mGlobalHelper = new GlobalHelper();
                    mGlobalHelper.setGlobalUserLoginInfo(userInfo);
                    Intent intent = new Intent(NaverLogin.this, SecondActivity.class);
                    startActivity(intent);
                    finish();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static class DeleteTokenTask extends AsyncTask<Context, Void, Boolean> {
        Context context;
        SecondActivity secondActivity;

        public DeleteTokenTask(Context mContext, SecondActivity mActivity) {
            this.context = mContext;
            this.secondActivity = mActivity;
        }
        @Override
        protected Boolean doInBackground(Context... contexts) {
            return OAuthLogin.getInstance().logoutAndDeleteToken(contexts[0]);
        }

        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);
            secondActivity.directToMainActivity(result);
        }
    }
}

GoogleLogin.class

package com.example.mysnsaccount;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;

import androidx.annotation.NonNull;

import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInResult;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.GoogleAuthProvider;

import java.util.ArrayList;
import java.util.List;

public class GoogleLogin extends Activity {
    private static final int RC_SIGN_IN = 9001;
    private GoogleSignInClient mGoogleClient;
    private FirebaseAuth mGoogleLoginModule;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mGoogleLoginModule = FirebaseAuth.getInstance();
        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();
        mGoogleClient = GoogleSignIn.getClient(getApplicationContext(), gso);

        if (mGoogleLoginModule.getCurrentUser() == null) {
            Intent signInIntent = mGoogleClient.getSignInIntent();
            startActivityForResult(signInIntent, RC_SIGN_IN);

        } else if (mGoogleLoginModule.getCurrentUser() != null) {
            List<String> userInfo = new ArrayList<>();
            GlobalHelper mGlobalHelper = (GlobalHelper) getApplication();
            userInfo.add(String.format("%s-%s", "GOOGLE", mGoogleLoginModule.getCurrentUser().getUid()));
            userInfo.add(mGoogleLoginModule.getCurrentUser().getDisplayName());
            GlobalHelper.setGlobalUserLoginInfo(userInfo);
            Intent intent = new Intent(GoogleLogin.this, SecondActivity.class);
            startActivity(intent);
            finish();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RC_SIGN_IN) {
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            if (result.isSuccess()) {
                GoogleSignInAccount account = result.getSignInAccount();
                firebaseAuthWithGoogle(account);
            } else {
                Toast.makeText(getApplicationContext(), "Google Sign In Failed", Toast.LENGTH_LONG).show();
                Intent intent = new Intent(GoogleLogin.this, MainActivity.class);
                startActivity(intent);
                finish();
            }
        }
    }

    private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {
        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        mGoogleLoginModule.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        if (task.isSuccessful()) {
                            if (mGoogleLoginModule.getCurrentUser() != null) {
                                List<String> userInfo = new ArrayList<>();
                                GlobalHelper mGlobalHelper = (GlobalHelper) getApplication();
                                userInfo.add(mGoogleLoginModule.getCurrentUser().getUid());
                                userInfo.add(mGoogleLoginModule.getCurrentUser().getDisplayName());
                                GlobalHelper.setGlobalUserLoginInfo(userInfo);
                                Intent intent = new Intent(GoogleLogin.this, SecondActivity.class);
                                startActivity(intent);
                                finish();
                            }
                        } else {
                            Toast.makeText(getApplicationContext(), "Google Authentication Failed", Toast.LENGTH_LONG).show();
                        }
                    }
                });
    }
}

SecondActivity

package com.example.mysnsaccount;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

public class SecondActivity extends AppCompatActivity {
    private TextView tvSecondUserID, tvSecondNickname;
    private Button btnSecondLogout, btnSecondResign;
    private List<String> userInfo = new ArrayList<>();

    Button.OnClickListener mLogoutListener = new Button.OnClickListener() {
        @Override
        public void onClick(View v) {
            GlobalAuthHelper.accountLogout(getApplicationContext(),SecondActivity.this);
        }
    };

    Button.OnClickListener mResignListener = new Button.OnClickListener() {
        @Override
        public void onClick(View v) {
            GlobalAuthHelper.accountResign(getApplicationContext(), SecondActivity.this);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        tvSecondUserID = findViewById(R.id.tv_second_userid);
        tvSecondNickname = findViewById(R.id.tv_second_nickname);
        btnSecondLogout = findViewById(R.id.btn_second_logout);
        btnSecondResign = findViewById(R.id.btn_second_resign);

        initView();

        btnSecondLogout.setOnClickListener(mLogoutListener);
        btnSecondResign.setOnClickListener(mResignListener);

    }

    private void initView() {
        GlobalHelper mGlobalHelper = new GlobalHelper();
        userInfo = mGlobalHelper.getGlobalUserLoginInfo();
        tvSecondUserID.setText("UserId : " + userInfo.get(0));
        tvSecondNickname.setText("Nickname : " + userInfo.get(1));
    }

    public void directToMainActivity(Boolean result) {
        Intent intent = new Intent(SecondActivity.this, MainActivity.class);
        if (result) {
            Toast.makeText(getApplicationContext(), "성공!", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(getApplicationContext(), "실패! 다시 로그인해주세요.", Toast.LENGTH_SHORT).show();
        }
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        startActivity(intent);
        finish();
    }
}

GlobalAuthHelper.class

package com.example.mysnsaccount;

import android.content.Context;
import android.widget.Toast;

import androidx.annotation.NonNull;

import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.FirebaseAuth;
import com.kakao.auth.ApiErrorCode;
import com.kakao.auth.Session;
import com.kakao.network.ErrorResult;
import com.kakao.usermgmt.UserManagement;
import com.kakao.usermgmt.callback.LogoutResponseCallback;
import com.kakao.usermgmt.callback.UnLinkResponseCallback;
import com.nhn.android.naverlogin.OAuthLogin;
import com.nhn.android.naverlogin.data.OAuthLoginState;

public class GlobalAuthHelper {

    public static void accountLogout(Context context, SecondActivity activity) {
        if (Session.getCurrentSession().checkAndImplicitOpen()) {
            UserManagement.getInstance().requestLogout(new LogoutResponseCallback() {
                @Override
                public void onCompleteLogout() {
                    activity.directToMainActivity(true);
                }
            });
        }  else if (OAuthLoginState.OK.equals(OAuthLogin.getInstance().getState(context))) {
            OAuthLogin.getInstance().logout(context);
            activity.directToMainActivity(true);
        } else if (FirebaseAuth.getInstance().getCurrentUser() != null) {
            FirebaseAuth.getInstance().signOut();
            GoogleSignIn.getClient(context, GoogleSignInOptions.DEFAULT_SIGN_IN).signOut();
            activity.directToMainActivity(true);
        }
    }

    public static void accountResign(final Context context, final SecondActivity activity) {
        if (Session.getCurrentSession().checkAndImplicitOpen()) {
            // 카카오 연동 해제
            UserManagement.getInstance().requestUnlink(new UnLinkResponseCallback() {
                @Override
                public void onFailure(ErrorResult errorResult) {
                    if (errorResult.getErrorCode() == ApiErrorCode.CLIENT_ERROR_CODE) {
                        Toast.makeText(context, "네트워크가 불안정합니다.", Toast.LENGTH_SHORT).show();
                        activity.directToMainActivity(false);
                    }
                }

                @Override
                public void onSessionClosed(ErrorResult errorResult) {
                    Toast.makeText(context, "세션이 닫혀있습니다.", Toast.LENGTH_SHORT).show();
                    activity.directToMainActivity(false);
                }

                @Override
                public void onSuccess(Long result) {
                    Toast.makeText(context, "카카오톡 연동 해제", Toast.LENGTH_SHORT).show();
                    activity.directToMainActivity(true);
                }
            });

        } else if (OAuthLoginState.OK.equals(OAuthLogin.getInstance().getState(context))) {
            // 네이버 연동 해제
            try {
                NaverLogin.DeleteTokenTask deleteTokenTask = new NaverLogin.DeleteTokenTask(context, activity);
                deleteTokenTask.execute(context).get();
            } catch (Exception e) {
                e.printStackTrace();
                activity.directToMainActivity(false);
            }

        } else if (FirebaseAuth.getInstance().getCurrentUser() != null) {
            // 구글 연동 해제
            try {
                FirebaseAuth.getInstance().getCurrentUser().delete().addOnCompleteListener(new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        if (task.isSuccessful()) {
                            activity.directToMainActivity(true);
                        }
                        else {
                            activity.directToMainActivity(false);
                        }
                    }
                }); // Firebase 인증 해제
                GoogleSignIn.getClient(context, GoogleSignInOptions.DEFAULT_SIGN_IN).revokeAccess(); // Google 계정 해제
            } catch (Exception e) {
                activity.directToMainActivity(false);
            }
        }
    }
}

마무리

이로써 Kakao, Naver, Google 로그인을 지원하는 방법에 대해 정리를 끝냈습니다. 각각에 대해서 구현하는 방법을 조금씩 달리할 수 있겠지만 여러 종류를 지원함에 있어 조금이나마 통일하고 클린하게 작성할 수 있게끔 고민을 해봤습니다. 자바를 제대로 공부한 적이 없어 쓰레기 코드가 있을 수 있으니 양해 부탁드리고 개선점이나 질문사항은 댓글로 부탁드립니다.

참고자료

+ Recent posts