읽기 전

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

관련 포스팅

Android에서 Naver 계정 연동하기

Android 사이드 프로젝트에 유저 계정관리를 로컬이 아닌 SNS 계정으로 사용하자는 의견이 있어 한국 유저들이 많이 이용하는 Kakao 계정에 이어 Naver 계정을 third party 앱에 연동하는 방법을 정리하려 한다.

Client ID, Client Secret 발급 및 등록

네이버 아이디로 로그인(이하 네아로)를 사용하기 위해 네이버 개발자 센터에 본인의 앱을 등록해야 한다.

개발자 센터 링크

네이버 계정으로 로그인 후 [Application] - [애플리케이션 등록]을 눌러 본인의 앱을 추가한다. 등록하고자 하는 애플리케이션 이름, 사용 API를 선택한다. 카카오와 마찬가지로 별명만 요청하기로 했다.

Android_SNS_Auth_002-01

안드로이드 앱이므로 안드로이드 플랫폼을 추가하면 다운로드 URL, 앱 패키지 이름을 입력하라고 한다. 다운로드 URL은 존재하지 않으므로 임의의 링크를 넣고 안드로이드 패키지 이름은 AnroidManifest.xml을 참고하면 확인할 수 있다.

Android_SNS_Auth_002-02

이로써 개발자 센터의 앱 등록을 모두 끝냈다. 내 애플리케이션의 개요 탭을 클릭하면 Client ID, Client Secret 값을 조회할 수 있다. 나중에 SDK 초기화 시 필요하므로 메모장에 적어두자.

Android_SNS_Auth_002-03

프로젝트에 Naver Login SDK import 하기

네이버 로그인 문서에서는 두 가지 방법을 소개하고 있지만 개인적으로 .arr 파일을 다운받고 적용하는 방법이 편해서 채택하였다.

네이버 로그인 .arr 모듈 파일 적용하기

네아로의 .arr파일은 github으로 관리되고 있다.

arr 파일 download 깃험 주소 - https://github.com/naver/naveridlogin-sdk-android

SDK 폴더에 접근 후 sdk.arr 파일을 프로젝트의 [app] - [libs] 폴더에 다운로드 한다.

build.gradle(Module)

.arr 파일을 다운받은 후 아래와 같이 추가한다.

dependencies {
    implementation project(path: ':naveridlogin_android_sdk_4.2.6')
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:support-core-utils:28.0.0'
    implementation 'com.android.support:customtabs:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
}

.arr 파일 프로젝트에 입력

Anroid Studio의 [File] - [Project Structure]를 클릭한 뒤 좌측 +를 눌러 모듈 추가를 한다. 모듈 타입은 .JAR/.ARR Package를 선택한다. 이후 경로선택 시 다운받았던 .arr 파일의 경로를 걸어준다. 적용한 뒤 build.gradle Sync를 하면 모듈이 성공적으로 프로젝트에 적용된다.

Android 네이버 로그인 구조

Android_SNS_Auth_002-04

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

로그인 레이아웃 정의

activity_main.xml

카카오와 마찬가지로 로그인 로직을 구현하기 위해 Naver에서 기본적으로 제공하는 버튼을 보이지 않게 배치하고 커스텀 버튼과 연결하는 방식을 채택하였다. 각 SNS 별로 제공하는 로그인 버튼의 양식이 달라 다른 유형도 함께 지원할 때는 이 방법이 적절했다.

<?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="네이버 로그인" />

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

네이버 로그인 SDK 초기화

카카오와 달리 앱 수준에서 SDK를 관리할 필요가 없어 GlobalHelper.class가 아니라 MainActivity에 네아로 SDK를 초기화하는 코드를 추가한다. 이후에 재호출하여도 토큰 갱신이 되지 않으니 한 번만 하면 된다. 처음에 받았던 Client ID와 Client Secret 값이 SDK 초기화에 쓰인다.

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

로그인 버튼 선언 및 핸들러 설정

커스텀 버튼 기반 로그인 로직을 구현하기 위해 기본적으로 제공하는 버튼에 커스텀 버튼 클릭 리스너를 연결한다.

MainActivity - 핸들러 정의

    @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);
            }
        }
    };

MainActivity - 버튼 선언 및 리스너 설정

        mNaverLoginBtn = findViewById(R.id.btn_naver_login);
        mNaverLoginBtnBasic = findViewById(R.id.btn_naver_login_basic);
        mNaverLoginBtnBasic.setOAuthLoginHandler(mNaverLoginHandler);
        mNaverLoginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mNaverLoginBtnBasic.performClick();
            }
        });

세션체크 및 자동 로그인

OAuthLoginState 객체를 호출하여 로그인이 필요한지, 초기화가 필요한지 체크한다. 세션이 없으면 버튼을 클릭하여 이전에 정의한 핸들러가 실행되고, 세션이 없으면 클릭없이 곧바로 로그인 절차를 거친다.

MainActivity - 네이버 세션 체크

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

MainActivity - 로그인 로직

        if (!HasNaverSession()) {
            // 핸들러가 세션 없을 시 동작을 대체한다.
        } else if (HasNaverSession()) {
            Intent intent = new Intent(MainActivity.this, NaverLogin.class);
            startActivity(intent);
            finish();
        }

토큰 인증 절차

네아로의 토큰 관리는 웹 기반으로 이루어진다. 발급받은 세션의 만료기한은 1시간이기 때문에 갱신 로직이 필요하다. 만료 기한도 조회할 수 있어 로그인 시 토큰 갱신 필요 여부를 체크하여 갱신할 수 있겠지만 귀찮으니 항상 갱신하게 하였다. 항상 갱신하게 했을 때 기능적으로 문제를 겪어본 적은 없었다. 그리고 사용자 정보를 요청할 때도 웹 기반이므로 AsyncTask 클래스로 작성하였다. Naver 로그인 관련 로직은 따로 Activity를 상속받는 NaverLogin.class를 생성하여 관리하였다. activity를 상속받지 않으니 AsyncTask와 같이 Thread를 다루는 동작에서 항상 그렇듯이 문제가 발생하여(네이버 로그인때문에 카카오도 activity를 상속받는 클래스를 만들었다.) 어쩔 수 없었다. 혹시 activity를 상속받지 않고 깔끔하게 class만으로 구현한 분이 계시면 가르침을 주세요..

NaverLogin.class - 접속 토큰 갱신 및 사용자 정보 요청

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()));
        }
    }

NaverLogin.class - 사용자 정보 요청

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)) {
                    // 정상 반환 시 반환코드는 "00"이다.
                    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();
            }
        }
    }

NaverLogin.class - 토큰 갱신 요청

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

네이버 로그아웃 / 앱 연동 해제(회원 탈퇴)

네이버 로그아웃

네이버 로그아웃은 로컬에서 토큰을 삭제하기 때문에 OAuthLogin 객체를 호출하여 로그아웃할 수 있다. 삭제하면 state가 NEED_LOGIN으로 바뀌어 MainActivity에서 작성했던 세션 체크 메소드에서 세션이 없음을 반환하게 된다.

GlobalAuthHelper.class - 로그아웃

    public static void accountLogout(Context context, SecondActivity activity) {
        if (OAuthLoginState.OK.equals(OAuthLogin.getInstance().getState(context))) {
            OAuthLogin.getInstance().logout(context);
            activity.directToMainActivity(true);
        }
    }

네이버 앱 연동해제(탈퇴하기)

네이버 앱 연동해제(회원 탈퇴)는 서버의 토큰을 함께 삭제해야 하므로 통신이 요구된다. 따라서 AsyncTask 클래스로 토큰 삭제를 요청하고 작업이 끝나고 MainActivity로 전환하는 동작을 수행한다. 만약 서버통신이 실패하더라도 로컬의 토큰이 삭제된 상태이므로 일단 재로그인 거치긴 해야한다. 그렇기 때문에 실패와 상관없이 PostExecute 메소드에 MainActivity로 보내는 메소드를 호출한다.

GlobalAuthHelper.class - 앱 연동 해제(탈퇴하기)

public static void accountResign(final Context context, final SecondActivity activity) {
        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);
            }
        }
    }

NaverLogin.class - 토큰 삭제 요청

 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);
        }
    }

전체 코드

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

        <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.Toast;

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;
    private LoginButton mKakaoLoginBtnBasic;
    private OAuthLoginButton mNaverLoginBtnBasic;
    private KakaoSessionCallback sessionCallback;
    private OAuthLogin mNaverLoginModule;
    private NaverLogin mNaverLoginAuth;
    final String NAVER_CLIENT_ID = "NAVER_CLIENT_ID";
    final String NAVER_CLIENT_SECRET = "NAVER_CLIENT_SECRET";

    @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);
            }
        }
    };


    @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);
        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();
            }
        });

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

        if (!HasKakaoSession() && !HasNaverSession()) {
            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();
        }
    }

    @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;
    }

    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.example.mysnsaccount.GlobalHelper;
import com.example.mysnsaccount.SecondActivity;
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 = "NAVER_CLIENT_ID";
    final String NAVER_CLIENT_SECRET = "NAVER_CLIENT_SECRET";
    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);
        }
    }
}

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.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;

import com.kakao.auth.ApiErrorCode;
import com.kakao.auth.ISessionCallback;
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);
        }
    }

    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);
            }

        }
    }
}

참고자료

+ Recent posts