일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 감사일기
- 카카오톡
- fanzeel
- AWS
- nextjs
- beanstalk
- node.js
- ios
- JavaScript
- node
- TypeScript
- angular
- S3
- 페이스북
- hybrid
- 알려줌
- Elastic Beanstalk
- swift
- angular4
- Route53
- 안드로이드
- php
- react
- 웹뷰
- 도메인
- Android
- cors
- NeXT
- 네이티브
- https
- Today
- Total
쪼렙 as! 풀스택
안드로이드 하이브리드앱 웹뷰 WebView 에서 카카오톡 로그인 (앱) 사용하기 본문
카카오톡 로그인 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" 은 서버에서 처리해준다.
필자는 페이스북 로그인도 이런식으로 처리하였고,
'공유'버튼을 눌렀을 때도 이런식으로 처리해서 네이티브 앱의 '공유' 기능을 사용할 수 있게 하였다.
'개발 일지 > Web & Server' 카테고리의 다른 글
PHP 코드 수정이 바로 적용되지 않을 때, opcache disable 시키기. (0) | 2020.11.19 |
---|---|
React - localhost 에서 cookie samesite 이슈 해결하기. (0) | 2020.09.06 |
MySQL, 정렬한 기준으로 1:1로 Join 해서 데이터 가져오기. group by 안쓰고 해결;; (0) | 2019.09.03 |
iamport 이용하여, 정기결제 붙이기. (0) | 2019.08.15 |
메일건 mailgun.com 에서 메일링 리스트 관리하기, 메일링 리스트에 속해있는 사람들에게 모두 메일 보내기. (0) | 2019.02.18 |