목차

티스토리 뷰

728x90
반응형

안녕하세요🐾

iOS 카카오톡 로그인 SDK 적용 과정을 정리하고자 글을 남깁니다.

 

다른 SNS 로그인 관련 SDK 적용은 아래의 글을 참고해 주세요!

 

2023.12.26 - [iOS/Swift] - [iOS] 구글 로그인 SDK 연동하기

2023.12.22 - [iOS/Swift] - [iOS] 페이스북 로그인 SDK 적용

2023.06.21 - [iOS/Swift] - [iOS] 네이버 로그인 SDK 연동하기

 

카카오톡 SDK는 개발자 페이지가 굉장히 잘 정리되어있고, API에 대한 상세 설명이 관련 문서와 코드 내 주석으로 잘 정리되어 있어 개발자 페이지만 보아도 큰 무리 없이 구현 가능할 정도라는 느낌을 받았고, 실제로 그랬습니다.

 

최근 사내 배포용을 위한 코코아팟 프레임워크를 만들고 API 문서 페이지를 구현했던 경험이 있던터라, 개발자가 쉽게 이해할 수 있도록 SDK를 제공하려는 개발자 친화적인 요소들에 많은 자극을 받으면서 아직 갈 길이 멀다는 것을 느낍니다. ㅠ_^ 그럼 시작해볼까유

 

※ 23년 6월 SDK 기준으로 작성된 글 입니다.

 


 

1. 설치

카카오톡 로그인 SDK는 라이브러리 의존성이 있어 카카오톡 SDK를 설치하면 아래의 라이브러리가  같이 설치됩니다.

저는 Cocoapods을 이용하여 설치를 진행하였습니다.

먼저 podfile에 들어갈 아래의 코드를 보실까요?

# 전체 추가
pod 'KakaoSDK'

# or

# 필요한 모듈 추가
pod 'KakaoSDKCommon'  # 필수 요소를 담은 공통 모듈
pod 'RxKakaoSDKCommon'
pod 'KakaoSDKAuth'  # 사용자 인증
pod 'RxKakaoSDKAuth'
pod 'KakaoSDKUser'  # 카카오 로그인, 사용자 관리
pod 'RxKakaoSDKUser'
pod 'KakaoSDKTalk'  # 친구, 메시지(카카오톡)
pod 'KakaoSDKStory'  # 카카오스토리 
pod 'KakaoSDKShare'  # 메시지(카카오톡 공유)
pod 'KakaoSDKTemplate'  # 메시지 템플릿 
pod 'KakaoSDKNavi'  # 카카오내비 
pod 'KakaoSDKFriend' # 카카오톡 소셜 피커, 리소스 번들 파일 포함

 

KakaoSDK에서 제공하는 모든 기능을 사용하려면 최상단의 pod KakaoSDK만 설치하면 되지만,

필요한 모듈만 골라 설치할 경우 하단에 나열된 pod 항목 중 필요한 것만 남기고 나머지는 삭제하시면 됩니다.

 

저는 이번 글에서 로그인 SDK만 사용할 것이고, RxSwift를 사용 할 때와, Swift만을 사용할 때, 두 가지의 케이스에 대해 모두 테스트 해보기 위해 아래와 같이 필요한 pod만 구성하였습니다.

# 카카오톡 로그인 관련
pod 'KakaoSDKCommon'  # 필수 요소를 담은 공통 모듈
pod 'RxKakaoSDKCommon'
pod 'KakaoSDKAuth'  # 사용자 인증
pod 'RxKakaoSDKAuth'
pod 'KakaoSDKUser'  # 카카오 로그인, 사용자 관리
pod 'RxKakaoSDKUser'

 

개발자 페이지에 나와있는 모듈간의 의존성은 아래 이미지와 같습니다.

로그인 SDK를 연동하기 위해서는 pod KakaoSDK{Common, Auth, User} 3개의 pod가 필요하다는 것을 알 수 있네요!

 

마지막으로 pod install 을 통해 설치를 진행해 주세요!

설치가 완료되면 .xworkspace를 오픈합니다.

 

2. 설정

2.1 애플리케이션 추가

먼저 kakao devlopers에서 로그인 연동에 사용할 애플리케이션을 아래와 같이 추가합니다.

 

추가된 앱을 선택하면 설정 화면이 나오게 됩니다.

카카오 로그인에서 활성화 설정ON으로 바꿔주세요.

OIDC(OpneID Connect)를 사용하려면 이 옵션 또한 활성화 시켜야 합니다.

 

 

카카오 로그인 시 얻어올 사용자의 정보에 대한 동의 항목도 아래와 같이 설정해 주세요!

 

이렇게 설정한 항목들은 아래와 같이 로그인 요청시 동의 요구 항목으로 나타납니다.

 

2.2 앱 허용

목록 설정

내 앱에서 카카오톡 앱을 실행시키기 위해 info.plist에 허용 목록을 작성해야 합니다.

 

아래와 같이 프로퍼티 리스트에서 직접 추가하시거나,

 

코드로 추가하고 싶다면 info.plist 마우스 우클릭 - open source code 후 아래의 코드를 입력

 <key>LSApplicationQueriesSchemes</key>
  <array>
      <!-- 카카오톡으로 로그인 -->
      <string>kakaokompassauth</string>
      <!-- 카카오톡 공유 -->
      <string>kakaolink</string>
 </array>

 

 

 

 

2.3 URL Schemes 설정

다음으로 URL Schemes에 카카오 로그인을 위한 네이티브 앱 키를 입력해야 합니다.

네이티브 앱 키는 아래와 같이 kakao developers에서 생성한 애플리케이션의 요약 정보 화면에서 확인 할 수 있습니다.

 

 

Xcode로 돌아와, 프로젝트 설정 - Info - URL Types - URL Schemes에서 kakao${네이티브앱키} 형식으로 URL Schemes를 넣어줍니다. (ex. 네이티브 앱 키가 1234이면, kakao1234)

 

그리고 또 하나의 URL Types을 만들고 kakao${네이티브앱키}://oauthURL Schemes에 추가해 주어야 합니다.

(ex. 네이티브 앱 키가 1234이면, kakao1234:ouath)

개발자 문서에 의하면,

첫번째는 URL Schemes는 카카오톡을 실행할 때

두번째는 URL Schemes는 카카오톡 로그인 후 서비스 앱으로 돌아올 때 필요하다고 하네요 :)

 

2.4 초기화 코드 및 Open URL설정

먼저 KakaoSDK 초기화 코드 추가가 필요합니다.

AppDelegate.swift에서 아래의 코드를 추가합니다.

주석에 표시한 것 처럼 네이티브 앱 키를 확인하여 넣어주세요!

 

Swift code:

더보기
import KakaoSDKCommon

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

  ...
  KakaoSDK.initSDK(appKey: "${NATIVE_APP_KEY}")
  
  // NATIVE_APP_KEY가 1234라면,
  //KakaoSDK.initSDK(appKey: "1234")
  ...

}

 

 

RxSwift code:

더보기
import RxKakaoSDKCommon

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

  ...
  RxKakaoSDK.initSDK(appKey: "${NATIVE_APP_KEY}")
  
  // NATIVE_APP_KEY가 1234라면,
  //RxKakaoSDK.initSDK(appKey: "1234")
  ...

}

 

 

마지막으로 카카오톡에서 프로젝트 앱으로 돌아왔을때 로그인 처리를 완료하기 위해 appDelegate.swift에서 아래의 코드도 추가합니다.

Swift Code:

더보기
import KakaoSDKAuth
...

class AppDelegate: UIResponder, UIApplicationDelegate {
    ...
    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        if (AuthApi.isKakaoTalkLoginUrl(url)) {
            return AuthController.handleOpenUrl(url: url)
        }

        return false
    }
    ...
}

 

RxSwift Code:

더보기
import RxKakaoSDKAuth
import KakaoSDKAuth
...

class AppDelegate: UIResponder, UIApplicationDelegate {
    ...
    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        if (AuthApi.isKakaoTalkLoginUrl(url)) {
            return AuthController.rx.handleOpenUrl(url: url)
        }

        return false
    }
    ...
}

 

 

만약 Deployment TargetiOS 13.0 이상 버전이신가요?

iOS 13.0 이후부터 앱의 생명주기 관리는 AppDelegate가, UI의 생명주기는 SceneDelegate에서 관리하도록 변경되었습니다.

따라서 배포 타겟이 13.0 이상인 프로젝트라면 UISceneDelegate.swift가 기본으로 실행되도록 설정되기 때문에 SceneDelegate.swift에 아래의 코드도 같이 입력합니다.

 

AppDelegate만 사용하도록 SceneDelegate를 제거하였거나, 프로젝트 배포 타겟이 iOS 13.0 이하라면, 아래의 코드는 생략하여도 됩니다.

 

Swift Code: 

더보기
import KakaoSDKAuth
...

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    ...
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        if let url = URLContexts.first?.url {
            if (AuthApi.isKakaoTalkLoginUrl(url)) {
                _ = AuthController.handleOpenUrl(url: url)
            }
        }
    }
    ...
}

 

 

RxSwift Code:

더보기
import RxKakaoSDKAuth
import KakaoSDKAuth
...

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    ...
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        if let url = URLContexts.first?.url {
            if (AuthApi.isKakaoTalkLoginUrl(url)) {
                _ = AuthController.rx.handleOpenUrl(url: url)
            }
        }
    }
    ...
}

 

 

3. 카카오톡 로그인

설정이 끝났다면 이제 로그인을 할 일만 남았네요 😎

3.1 로그인 요청

로그인 화면은 위와 같이 간단하게 만들어 보았습니다. 

카카오 로그인 버튼을 누르면 로그인 요청을 진행하는 func 블록 내에 아래의 코드를 넣어줍니다.

 

Swift Code:

더보기
// 카카오톡 로그인 버튼을 누르면 실행되는 코드
// 카카오톡 실행 가능 여부 확인
if (UserApi.isKakaoTalkLoginAvailable()) {
    UserApi.shared.loginWithKakaoTalk {(oauthToken, error) in
        if let error = error {
            print(error)
        }
        else {
            print("loginWithKakaoTalk() success.")

           //let idToken = oAuthToken.idToken ?? ""
           //let accessToken = oAuthToken.accessToken
           
           self.kakaoGetUserInfo()         
        }
    }    
}

 

 

RxSwift Code:

더보기
// Class member property
let disposeBag = DisposeBag()

// 카카오톡 실행 가능 여부 확인
if (UserApi.isKakaoTalkLoginAvailable()) {   
    UserApi.shared.rx.loginWithKakaoTalk()
        .subscribe(onNext:{ (oauthToken) in
            print("loginWithKakaoTalk() success.")
        
            //let idToken = oAuthToken.idToken ?? ""
           //let accessToken = oAuthToken.accessToken
           
           self.kakaoGetUserInfo()
        }, onError: {error in
            print(error)
        })
    .disposed(by: disposeBag)
}

 

3.2 로그인 정보 가져오기

3.1에서 호출하는 kakaoGetUserInfo() 는 사용자의 정보를 가져오는 코드입니다.

 

Swift Code:

더보기
/// 사용자 정보 가져오기
private func kakaoGetUserInfo() {
    UserApi.shared.me() { (user, error) in
        if let error = error {
            print(error)
        }
        
        let userName = user.kakaoAccount?.name
        let userEmail = user.kakaoAccount?.email
        let userGender = user.kakaoAccount?.gender
        let userProfile = user.kakaoAccount?.profile?.profileImageUrl
        let userBirthYear = user.kakaoAccount?.birthyear

        let contentText =
        "user name : \(userName)\n userEmail : \(userEmail)\n userGender : \(userGender), userBirthYear : \(userBirthYear)\n userProfile : \(userProfile)"

        print("user - \(user)")

        if userEmail == nil {
            self.kakaoRequestAgreement()
            return
        }

        self.textField.text = contentText
}

 

 

RxSwift Code:

더보기
/// 사용자 정보 가져오기
private func kakaoGetUserInfo() {
    UserApi.shared.rx.me()
        .subscribe (onSuccess:{ user in
            print("me() success.")

            //do something
            let userName = user.kakaoAccount?.name
            let userEmail = user.kakaoAccount?.email
            let userGender = user.kakaoAccount?.gender
            let userProfile = user.kakaoAccount?.profile?.profileImageUrl
            let userBirthYear = user.kakaoAccount?.birthyear

            let contentText =
            "user name : \(userName)\n userEmail : \(userEmail)\n userGender : \(userGender), userBirthYear : \(userBirthYear)\n userProfile : \(userProfile)"

            print("user - \(user)")

            if userEmail == nil {
                self.kakaoRequestAgreement()
                return
            }

            self.textField.text = contentText

        }, onFailure: {error in
            print(error)
        })
        .disposed(by: disposeBag)
}

 

사용자 정보를 가져와 표시하였습니다. 만약 사용자 정보 제공 동의를 하나도 하지 않았다면,

위 코드 중 if userEmail == nil 과 같은 조건문에 걸리게 되겠죠?

해당 function에서 추가 동의를 요구하는 코드가 필요합니다.

 

3.3 추가 동의 요청하기

동의하지 않은 항목에 대해 추가 동의 요청 항목 화면을 안내하는 코드는 아래와 같습니다.

Swift Code:

더보기
/// 추가 항목 동의받기
private func kakaoRequestAgreemenft() {
    // 추가 항목 동의 받기(사용자가 동의하지않은 항목에 대한 추가 동의 요청
    UserApi.shared.me() { (user, error) in
        if let error = error {
            print(error)
        }
        else {
            guard let user = user else { return }
            var scopes = [String]()
            if (user.kakaoAccount?.profileNeedsAgreement == true) { scopes.append("profile") }
            if (user.kakaoAccount?.emailNeedsAgreement == true) { scopes.append("account_email") }
            if (user.kakaoAccount?.birthdayNeedsAgreement == true) { scopes.append("birthday") }
            if (user.kakaoAccount?.birthyearNeedsAgreement == true) { scopes.append("birthyear") }
            if (user.kakaoAccount?.genderNeedsAgreement == true) { scopes.append("gender") }
            if (user.kakaoAccount?.phoneNumberNeedsAgreement == true) { scopes.append("phone_number") }
            if (user.kakaoAccount?.ageRangeNeedsAgreement == true) { scopes.append("age_range") }
            if (user.kakaoAccount?.ciNeedsAgreement == true) { scopes.append("account_ci") }

            if scopes.count > 0 {
                print("사용자에게 추가 동의를 받아야 합니다.")

                // OpenID Connect 사용 시
                // scope 목록에 "openid" 문자열을 추가하고 요청해야 함
                // 해당 문자열을 포함하지 않은 경우, ID 토큰이 재발급되지 않음
                // scopes.append("openid")

                //scope 목록을 전달하여 카카오 로그인 요청
                UserApi.shared.loginWithKakaoAccount(scopes: scopes) { (_, error) in
                    if let error = error {
                        print(error)
                    }
                    else {
                        UserApi.shared.me() { (user, error) in
                            if let error = error {
                                print(error)
                            }
                            else {
                                print("me() success.")
                                guard let user = user else { return }

                                //do something
                                let userName = user.kakaoAccount?.name
                                let userEmail = user.kakaoAccount?.email
                                let userGender = user.kakaoAccount?.gender
                                let userProfile = user.kakaoAccount?.profile?.profileImageUrl
                                let userBirthYear = user.kakaoAccount?.birthyear

                                let contentText =
                                "user name : \(userName)\n userEmail : \(userEmail)\n userGender : \(userGender), userBirthYear : \(userBirthYear)\n userProfile : \(userProfile)"

                                self.textField.text = contentText
                            }
                        }
                    }
                }
            }
            else {
                print("사용자의 추가 동의가 필요하지 않습니다.")
            }
        }
    }
}

 

 

RxSwift Code:

더보기
/// 추가 항목 동의받기
private func kakaoRequestAgreement() {
    // 추가 항목 동의 받기(사용자가 동의하지않은 항목에 대한 추가 동의 요청
    UserApi.shared.rx.me()
        .map({ (user) -> User in

            //필요한 scope을 아래의 예제코드를 참고해서 추가한다.
            //아래 예제는 모든 스콥을 나열한것.
            var scopes = [String]()

            if (user.kakaoAccount?.profileNeedsAgreement == true) { scopes.append("profile") }
            if (user.kakaoAccount?.emailNeedsAgreement == true) { scopes.append("account_email") }
            if (user.kakaoAccount?.birthdayNeedsAgreement == true) { scopes.append("birthday") }
            if (user.kakaoAccount?.birthyearNeedsAgreement == true) { scopes.append("birthyear") }
            if (user.kakaoAccount?.genderNeedsAgreement == true) { scopes.append("gender") }
            //if (user.kakaoAccount?.phoneNumberNeedsAgreement == true) { scopes.append("phone_number") }
            if (user.kakaoAccount?.ageRangeNeedsAgreement == true) { scopes.append("age_range") }
            if (user.kakaoAccount?.ciNeedsAgreement == true) { scopes.append("account_ci") }

            if (scopes.count > 0) {
                print("사용자에게 추가 동의를 받아야 합니다.")
                self.textField.text = "사용자에게 추가 동의를 받아야 합니다."
                // OpenID Connect 사용 시
                // scope 목록에 "openid" 문자열을 추가하고 요청해야 함
                // 해당 문자열을 포함하지 않은 경우, ID 토큰이 재발급되지 않음
                // scopes.append("openid")

                // scope 목록을 전달하여 SdkError 처리
                throw SdkError(scopes: scopes)
            }
            else {
                print("사용자의 추가 동의가 필요하지 않습니다.")
                self.textField.text = "사용자의 추가 동의가 필요하지 않습니다."

                return user
            }
        })
        .retry(when: Auth.shared.rx.incrementalAuthorizationRequired())
        .subscribe(onSuccess:{ ( user ) in
            print("me() success.")

            //do something
            let userName = user.kakaoAccount?.name
            let userEmail = user.kakaoAccount?.email
            let userGender = user.kakaoAccount?.gender
            let userProfile = user.kakaoAccount?.profile?.profileImageUrl
            let userBirthYear = user.kakaoAccount?.birthyear

            let contentText =
            "user name : \(userName)\n userEmail : \(userEmail)\n userGender : \(userGender), userBirthYear : \(userBirthYear)\n userProfile : \(userProfile)"

            self.textField.text = contentText

        }, onFailure: {error in
            print(error)
        })
        .disposed(by: disposeBag)
}

 

4. 카카오톡 로그아웃

로그아웃은 Logout, Unlink 두 개의 API가 지원됩니다.

4.1 로그아웃

로그아웃은 사용자 액세스 토큰과 리프레시 토큰을 모두 만료시켜 사용자 정보로 API를 호출할 수 없도록하고, 해당 API를 호출하면 요청 성공 여부와 관계없이 토큰을 삭제 처리합니다.

 

Swift Code:

더보기
/// 로그아웃
private func kakaoLogout() {
    // 사용자 액세스 토큰과 리프레시 토큰을 모두 만료시켜, 더 이상 해당 사용자 정보로 카카오 API를 호출할 수 없도록 합니다.
    UserApi.shared.logout {(error) in
        if let error = error {
            print(error)
        }
        else {
            print("logout() success.")
        }
    }
}

 

 

RxSwift Code:

더보기
/// 로그아웃
private func kakaoLogout() {
    // 사용자 액세스 토큰과 리프레시 토큰을 모두 만료시켜, 더 이상 해당 사용자 정보로 카카오 API를 호출할 수 없도록 합니다.
    UserApi.shared.rx.logout()
        .subscribe(onCompleted:{
            print("logout() success.")
            self.textField.text = "logout() success."
        }, onError: {error in
            print(error)
        })
        .disposed(by: disposeBag)
}

 

4.2 언링크

언링크는 연결 끊기 요청 성공시, 로그아웃 처리가 함께 이뤄져 토큰이 삭제됩니다. 

연결 해제 요청이 성공될때만 로그아웃이 수행되는 API입니다.

 

Swift Code:

더보기
/// 연결 끊기
private func kakaoUnlink() {
    // 연결 끊기 요청 성공 시 로그아웃 처리가 함께 이뤄져 토큰이 삭제됩니다.
    UserApi.shared.unlink {(error) in
        if let error = error {
            print(error)
        }
        else {
            print("unlink() success.")
        }
    }
}

 

 

RxSwift Code:

더보기
/// 연결 끊기
private func kakaoUnlink() {
    // 연결 끊기 요청 성공 시 로그아웃 처리가 함께 이뤄져 토큰이 삭제됩니다.
    UserApi.shared.rx.unlink()
        .subscribe(onCompleted:{
            print("unlink() success.")
            self.textField.text = "unlink() success."
        }, onError: {error in
            print(error)
        })
        .disposed(by: disposeBag)
}

 


 

 

지금까지 KakaoSDK를 이용한 로그인 API를 알아보았습니다.

혹시나 틀린 부분이 있다면 알려주세요 감사합니다 :)

 

 

5. 참고

반응형
댓글
300x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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
글 보관함