쪼렙 as! 풀스택

안드로이드 하이브리드앱 웹뷰 WebView 에서 카카오톡 로그인 (앱) 사용하기 본문

개발 일지/Web & Server

안드로이드 하이브리드앱 웹뷰 WebView 에서 카카오톡 로그인 (앱) 사용하기

코코앱 2019. 11. 23. 13:57

카카오톡 로그인 Javascript SDK 로 개발했을 때,

안드로이드 모바일 브라우저에서는 자동으로 '카카오톡 앱'이 호출된다.

 

문제는 하이브리드앱으로 '웹뷰'를 패키징한 형태로 서비스를 하게 될때는,

카카오톡 로그인을 하면 우리가 원하는대로 '카카오톡 앱' 이 호출되지 않고,

웹 상태에서 '카카오 로그인' 폼이 뜨게 된다.

 

사실 사용자 입장에서는 '하이브리드앱'인지 '웹뷰'서비스인지 알리가 만무하고,

카카오톡 로그인을 누르면, 당연히 사용하고 있는 카카오톡 앱이 호출되지 않고,

이메일부터 일일히 입력해야하는 '카카오 로그인' 폼 웹화면이 뜬다면, 얼마나 불편한 서비스라 생각하겠는가.

 

이것을 해결하기 위해 벼라별 방법을 다 찾아봤다.

ChromeClient 와 WebClient 를 오버라이드해서, 새로운 창이 뜨려고 하면,

다이얼로그로 띄운다던지, 새로운 웹뷰를 addView 해서 보여준다던지, 별짓을 다 해봤지만,

나의 실력부족인지 몰라도, 여튼 웹뷰에서 카카오 로그인 눌렀을때, '카톡 네이티브앱' 을 호출하는건 모두 실패했다.

 

그래서 조금 귀찮지만, 웹뷰에서 '카카오로그인'을 누르면, 카카오 네이티브앱 SDK 를 호출하고 후처리로 웹뷰로 다시 넘기는 방식으로 결정했다.

 

내가 해결한 방식을 요약하자면 아래와 같다.

1. 웹뷰의 User-Agent 를 수정하여, 하이브리드 앱의 '웹뷰'환경이라는것을 기록해둔다.

2. 웹 상태에서 '카카오로그인'을 눌렀을 때, User-Agent 를 검사하여 '하이브리드앱 웹뷰' 인 경우에는, '특정 URL' 로 넘어가도록 해준다, (웹뷰가 아니라 그냥 모바일 브라우저 환경일 때는, 카카오 Javascript SDK 에서 자동으로 '카톡 네이티브앱'을 호출한다)

3. 웹뷰의 'shouldOverrideUrlLoading()' 을 오버라이드 하여, '특정 URL'로 이동하려는 경우, 카카오 - 안드로이드 네이티브 SDK 를 이용해 카카오톡 로그인을 시도한다.

4. 네이티브단에서 카톡로그인이 성공하면, 결과를 웹뷰에 알려준다.

 

나는 웹개발과 네이티브 앱 개발을 동시에 하기 때문에, 이런방식을 선택하였는데,

Web개발자와 App개발자의 역할이 나누어져 있다면, 면밀한 협업이 필요하다.

 

자세한 코드는 아래로.

 

0. 준비물 - 카카오 SDK 안드로이드 네이티브 셋팅과, Javascript 셋팅은 이미 완료되어있는것으로 간주한다.

 

1. 안드로이드 User-Agent 수정하기. MainActivity.java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    setContentView(R.layout.activity_main);

    //웹뷰 세팅들 이것저것...
    mWebview = findViewById(R.id.webView);
    WebSettings settings = mWebview.getSettings();
    settings.setJavaScriptEnabled(true);
    settings.setAllowContentAccess(true);
    settings.setAllowFileAccess(true);
    CookieManager cm = CookieManager.getInstance();
    cm.setAcceptCookie(true);
    cm.setAcceptThirdPartyCookies(mWebview, true);
    //mWebview.setWebChromeClient(new MyChromeClient());
    mWebview.setWebViewClient(new MyWebViewClient());

    ...

    //여기에서 User-Agent를 수정해준다.
    String agentNew = settings.getUserAgentString() + " MY_HYBRID_APP";
    settings.setUserAgentString(agentNew);

    mWebview.loadUrl("https://MYDOMAIN.COM/");    
    
    ...
}

 

2. 웹 환경 자바스크립트 에서 카카오로그 버튼을 눌렀을 때, 분기 해주기.

clickKakaoLogin = () => {

  //일단 userAgent 를 가져와서, 네이티브 앱인지 알아오자.
  if(/MY_HYBRID_APP/i.test(navigator.userAgent)) {
    console.log('하이브리드앱에서 호출하였다.')
    window.location.href = '/MY_KAKAO_LOGIN_URL';
    return;
  }
  
  //일반적인 브라우저 환경이라면, 카카오 Javascript SDK 를 이용한 카카오로그인을 시도한다.
  const Kakao = window.Kakao
  try { Kakao.init('JAVASCRIPT-APP-KEY'); }
  catch(err) { console.error(err) }
  
  Kakao.Auth.login({
    ...
  });
}

 

3-1. 웹뷰의 'shouldOverrideUrlLoading()' 을 오버라이드 하기.

MainActivity.java - 

class MyWebViewClient extends WebViewClient {

    ....
    
    private final int REQ_CODE = 111;

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        return checkUrl(url);
    }

    @TargetApi(Build.VERSION_CODES.N)
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        String url=request.getUrl().toString();
        return checkUrl(url);
    }

    private boolean checkUrl(String url) {
    	//웹뷰 환경에서 '카카오로그인'버튼을 눌러서 MY_KAKAO_LOGIN_URL 로 이동하려고 한다.
        if(url.contains("/MY_KAKAO_LOGIN_URL")) {
            //실제 카카오톡 로그인 기능을 실행할 LoginActivity 를 실행시킨다.
            Intent intent = new Intent(this, LoginActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivityForResult(intent, REQ_CODE);
            return true;	//리턴 true 하면, 웹뷰에서 실제로 위 URL 로 이동하지는 않는다.
        }
        return false;
    }
}

 

3-2, 카카오 네이티브 SDK 의 카톡로그인 실행하기.

LoginActivity.java

public class LoginActivity extends AppCompatActivity {

    private boolean isNeedLogin = true;

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Session.getCurrentSession().removeCallback(mKakaoSessionCallback);
    }

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

        Session.getCurrentSession().close();
        Session.getCurrentSession().addCallback(mKakaoSessionCallback);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if(isNeedLogin) {
            isNeedLogin = false;
            Session.getCurrentSession().open(AuthType.KAKAO_LOGIN_ALL, this);
        }
    }


    /**
     * 카카오 세션 콜백.
     */
    private ISessionCallback mKakaoSessionCallback = new ISessionCallback() {
        @Override
        public void onSessionOpened() {

            if(Session.getCurrentSession().isOpened()){
                Log.e("LoginActivity", "카카오 로그인 성공");
            }

            List<String> keys = new ArrayList<>();
            keys.add("kakao_account.profile");
            keys.add("kakao_account.email");

            UserManagement.getInstance().me(keys, new MeV2ResponseCallback() {
                @Override
                public void onSessionClosed(ErrorResult errorResult) {
                    finishWithError(errorResult.getErrorMessage());
                }

                @Override
                public void onSuccess(MeV2Response result) {
                    Log.e("LoginActivity", "카카오톡 프로필 가져오기 성공했다.");
                    String kakaoId = result.getId() +"";
                    String email = result.getKakaoAccount().getEmail();
                    String koAccessToken = Session.getCurrentSession().getTokenInfo().getAccessToken();
                    String photoUrl = result.getKakaoAccount().getProfile().getProfileImageUrl();
                    String name = result.getKakaoAccount().getProfile().getNickname();

                    finishWithSuccess(name, email, photoUrl, kakaoId, koAccessToken);
                }

                @Override
                public void onFailure(ErrorResult errorResult) {
                    super.onFailure(errorResult);
                    finishWithError(errorResult.getErrorMessage());
                }
            });
        }

        @Override
        public void onSessionOpenFailed(KakaoException ex) {
            Log.e("LoginActivity", "온 쎄션 오픈 페일드.");
            finishWithError(ex.getLocalizedMessage());
        }
    };


    private void finishWithError(String err) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(" 로그인");
        builder.setMessage(err);
        builder.setCancelable(false);
        builder.setPositiveButton("확인", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                finish();
            }
        }).show();
    }


    private void finishWithSuccess(String name, String email, String photoUrl, String kkId, String kkAccessToken) {
        Intent intent = new Intent();
        intent.putExtra("name", name);
        intent.putExtra("email", email);
        intent.putExtra("photoUrl", photoUrl);
        intent.putExtra("kkId", kkId);
        intent.putExtra("kkAccessToken", kkAccessToken);
        setResult(Activity.RESULT_OK, intent);
        finish();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (Session.getCurrentSession().handleActivityResult(requestCode, resultCode, data)) {
            return;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }
}

 

 

4. 로그인 결과를 웹뷰에 알려준다.

MainActivity.java

    ...
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == REQ_CODE && resultCode == Activity.RESULT_OK) {
            String name = data.getStringExtra("name");
            String email = data.getStringExtra("email");
            String photoUrl = data.getStringExtra("photoUrl");
            String kkId = data.getStringExtra("kkId");
            String kkAccessToken = data.getStringExtra("kkAccessToken");

            String url = "https://MY_DOMAIN.COM/HANDLE_LOGIN_URL";
            url += "?kkAccessToken="+kkAccessToken;
            if(email != null){
                url += "&email="+email;
            }
            if(photoUrl != null){
                url += "&photoUrl="+photoUrl;
            }
            if(kkId != null){
                url += "&kkId="+kkId;
            }
            if(name != null){
                url += "&name="+name;
            }
            this.mWebview.loadUrl(url);
        }
    }

 

5. 로그인 성공 후처리 URL "https://MY_DOMAIN.COM/HANDLE_LOGIN_URL" 은 서버에서 처리해준다.

 

필자는 페이스북 로그인도 이런식으로 처리하였고,

'공유'버튼을 눌렀을 때도 이런식으로 처리해서 네이티브 앱의 '공유' 기능을 사용할 수 있게 하였다.

 

 

Comments