본문 바로가기

Swift/개인프로젝트

[Swift] Petpion - Apple, Kakao Login with OAuth

OAuth (Open Authorization)

OAuth는 인증을 위한 수단(프로토콜)입니다. 우린 수많은 웹, 앱 서비스의 아이디와 비밀번호를 모두 기억할 수 없기에 OAuth를 통해 몇몇의 다른 서비스의 접근 권한을 다른 애플리케이션에서도 사용할 수 있도록 하는 것이죠.

OAuth의 초기버전인 1.0 과 현재 상용화된 2.0 버전의 차이점은 웹 애플리케이션에서 처음 사용된 버전이 1.0 버전, 이를 애플리케이션 지원 강화한 것이 2.0 버전입니다.

즉, 위 사진에 SNS 계정으로 로그인 부분이 OAuth를 활용한 것 입니다.

요즘은 새로운 서비스를 이용할 때 대부분 저 기능이 삽입되어 있고 많이 사용하기도 하죠.

OAuth 2.0 Flow (출처: https://cloud.google.com/apigee/docs/api-platform/security/oauth/oauth-introduction?hl=ko )

OAuth 2.0의 절차를 간략히 설명하면 다음과 같습니다.

  1. 클라이언트(애플리케이션)는 사용자(리소스 소유자)가 보호하는 리소스에 접근하고자 합니다. 이를 위해 클라이언트는 서비스 제공자(인증 서버)에게 인증을 요청합니다.
  2. 인증 서버는 사용자 인증을 수행하고, 클라이언트에게 액세스 토큰을 발급합니다.
  3. 클라이언트는 액세스 토큰을 사용하여 보호된 리소스에 접근합니다.
  4. 리소스 서버는 액세스 토큰을 검증하고, 클라이언트가 보호된 리소스에 접근할 수 있는지 확인합니다.
  5. 만약 액세스 토큰이 유효하지 않다면, 클라이언트는 인증 서버에게 새로운 액세스 토큰을 요청합니다.

 

Token ( Access Token, Refresh Token)

OAuth가 결국 Authorization Server에서 Access Token을 발급받아서 이를 활용해 원하는 정보를 얻는 방식이라면 Access Token은 정확히 무엇일까요?

정확히는 서버 요청 시 사용하는 문자열입니다. 클라이언트(앱)는 이것을 읽거나 해석할 수 없고 단순히 요청을 보내는 클라이언트의 신원을 증명하는 데 사용됩니다. 즉 서버에 데이터를 가져오기 위한 암호화된 열쇠인 거죠.

그러나 Authorization Server에서 인증해 준 인증정보(id, pw)를 당연히 Client(앱)에 계속 제공할 수 없기에 Access Token은 보안상 이유로 짧은 유효기간을 가지고 있습니다. 유효기간이 끝나게 되면 다시는 그 정보에 접근하지 못하게 됩니다.

이 상황을 대처하기 위해 만들어둔 것이 Refresh Token입니다.

Refresh Token을 사용하여 새로운 Access Token을 발급받을 수 있습니다. 이렇게 함으로써 사용자는 로그인 과정을 다시 거치지 않아도 되고, 서비스 제공자는 Access Token을 자주 발급해야 하는 부담을 줄일 수 있습니다.

Refresh Token은 Access Token과는 달리 만료 기간이 길고, 보안에 더 중점을 둡니다. 따라서 Refresh Token은 유출되지 않도록 보안 조치가 강화되어 있으며, 사용자 인증에 대한 권한을 가지고 있는 클라이언트만이 이를 발급받을 수 있습니다.

 

Kakao Login

이것을 Petpion에서 사용자 A 가 카카오로 로그인 을 하는 과정으로 예를 들어 설명을 한다면

  1. 앱에서 Kakao Login SDK를 사용하여 사용자가 로그인할 수 있는 앱/ 웹 페이지로 이동합니다 - Authorization request
  2. 사용자가 카카오 계정으로 로그인한 후, Petpion 앱으로 돌아갑니다. - Authorization grant
  3. Petpion앱에서 사용자가 승인한 인증정보(id, pw)를 Kakao 인증 서버에 전달하고 Kakao 인증 서버에서 우리 고객이 맞는지 확인합니다.
  4. Authorization Server에서 Access token을 발급해 줍니다.
  5. 발급받은 Access token을 Kakao API 서버에 전달합니다
  6. Kakao API 서버에 제한된 리소스를 공급받는다 (getKakaoUserID)

위 플로우를 구현한 코드를 통해 확인해 보겠습니다.

// DafaultLoginUseCase

public func getKakaoUserID(_ completion: @escaping ((String) -> Void)) {
        kakaoAuthReporitory.startKakaoLogin { [weak self] kakaoLoginFinished in
            if kakaoLoginFinished {
                self?.kakaoAuthReporitory.getKakaoUserIdentifier { kakaoUserID in
                    completion(kakaoUserID)
                }
            }
        }
    }

// DefaultKakaoAuthRepository
func startKakaoLogin(_ completion: @escaping ((Bool) -> Void)) {
        if UserApi.isKakaoTalkLoginAvailable() == true {
            kakaoLoginWithApp { result in
                completion(result)
            }
        } else {
            kakaoLoginWithWeb { result in
                completion(result)
            }
        }
    }

func getKakaoUserIdentifier(_ completion: @escaping ((String)-> Void)) {
        UserApi.shared.me() { (user, error) in
            if let error = error {
                print(error.localizedDescription)
            }
            
            if let userID = user?.id {
                completion(String(describing: userID))
            }
        }
    }

...

// MARK: - Login with App/Web
    func kakaoLoginWithApp(_ completion: @escaping ((Bool) -> Void)) {
        UserApi.shared.loginWithKakaoTalk {(token, error) in
            if let error = error {
                print(error)
                completion(false)
            }
            else {
                print("token?.accessToken): \\(String(describing: token?.accessToken))")
                print("token?.refreshToken): \\(String(describing: token?.refreshToken))")
                print("kakaoLoginWithApp() success.")
                completion(true)
            }
        }
    }

    func kakaoLoginWithWeb(_ completion: @escaping ((Bool) -> Void)) {
        UserApi.shared.loginWithKakaoAccount {(_, error) in
            if let error = error {
                print(error)
                completion(false)
            }
            else {
                print("kakaoLoginWithApp() success.")
                completion(true)
            }
        }
    }

위 코드에서 getKakaoUserID 메서드는 카카오톡으로 로그인하여 accessToken을 발급받는고 accessToken을 통해 KakaoUseID를 발급받는 것까지의 코드입니다.

 

startKakaoLogin 메서드에서

  1. if UserApi.isKakaoTalkLoginAvailable() == true를 통하여 카카오톡이 깔려있는지를 확인하고 있습니다. 깔려있을 시, 카카오톡앱을, 안 깔려있다면 카카오톡 로그인 웹을 실행합니다.
  2. 사용자가 로그인을 하면 다시 카카오톡 앱/웹에서 Petpion으로 돌아옵니다
  3. 이후 kakaoLoginWithApp/Web 메서드 내에 UserApi.shared.loginWithKakaoTalk를 통해 카카오 인증 서버에 고객의 로그인정보를 확인합니다.
  4. 로그인 결과를 (OAuthToken?, Error?) 타입으로 반환받습니다. 로그인이 정상적으로 작동했으면 OAuthToken가, 실패 시 Error 가 반환될 것입니다. 그리고 그 결괏값을 이스케이핑 클로져로 Bool타입을 담아 보내줍니다.
  5. getKakaoUserID 메서드에서 kakaoAuthReporitory.startKakaoLogin 메서드의 콜백으로 로그인 성공 여부를 Bool 타입으로 받았고 성공 시 kakaoAuthReporitory.getKakaoUserIdentifier 메서드를 호출합니다.
  6. getKakaoUserIdentifier 메서드를 통하여 KakaoID를 발급받았습니다.

 

Apple Login

Apple은 사용자 개인 정보 보호 및 보안을 강화하기 위해 Sign in with Apple을 개발하여 2019년 WWDC 에서 발표했으며, 이 프로토콜은 OAuth 2.0을 확장하여 Apple ID 인증 서비스를 이용한 사용자 인증 및 권한 부여를 지원합니다.

 

가장 명확한 차이점은 기존 Kakao를 비롯한 OAuth2.0 프로토콜을 준수하는 방식에서는 로그인 페이지를 제공받아 사용자가 Apple ID를 입력하고 인증을 거친 후 access token과 refresh token을 발급받았다면, Apple Login에서는 사용자가 Apple ID와 비밀번호를 직접 입력하는 로그인 페이지를 제공하지 않고 Apple의 인증 서버에서 애플리케이션에서 제공한 고유한 클라이언트 식별자와 함께 Identity Token을 생성하여 반환합니다. 이때 Identity Token에는 사용자가 인증되었다는 것을 증명하는 정보와 애플의 서비스에서 사용 가능한 사용자 정보가 포함됩니다. 이를 통해 애플의 인증 서버는 Identity Token의 유효성을 검증하고, 인증된 사용자의 정보(Apple ID Credential)를 제공합니다.

 

다만, Apple의 인증 서버가 아닌 다른 서비스 인증을 위해 apple Login을 사용한다면 , Identity Token을 검증하여 https://appleid.apple.com/auth/token 주소에 POST로 특정 정보들을 넘겨서 OAuth2.0 프로토콜에서 사용하는 Access Token과 Refresh Token을 발급받을 수 있습니다. 당연히 이렇게 발급받은 Access Token은 Apple API 서버에서 사용자 정보를 요청하는 것이 불가능합니다. 또한 현재 Apple에서는 발급받은 access token을 통하여 인증해 줄 서비스를 제공하지 않고 있고, refresh token을 통하여 session을 유지하는 방법을 사용해야 합니다.

 

토큰의 성격 또한 차이가 있습니다. OAuth 2.0의 access token은 일반적으로 문자열 형태의 랜덤 한 값으로 구성되어 있어 다양한 플랫폼에서 권한을 행사할 수 있게 해 줌으로써 리소스 접근이 가능하게 하는데 목적을 두고 있다면, Identity token은 JWT(JSON Web Token) 토큰 형태로 사용자의 인증 정보를 포함하는 토큰입니다.

 

Apple의 인증 서비스를 통해 사용자 인증을 수행할 때, 앱과 Apple의 인증 서버 간에 전송되며, 사용자 ID 및 이메일과 같은 개인 정보가 포함됩니다. Identity Token은 검증을 위해 공개키/비공개키 암호화 기술을 사용하여 서명됩니다. 따라서 Identity Token은 클라이언트와 인증 서버 간의 안전한 통신을 보장하기 위해 사용됩니다.

 

결국 accessToken(Apple 인증서버에서는 Identity Token)을 사용하여 Protected Resource(Apple ID Credential)를 제공받는 점에서 OAuth2.0 프로토콜과 유사하다고 위와 같은 명백한 차이점들이 있습니다.

 

 

출처:

NAVER D2

Introducing Sign In with Apple - WWDC19 - Videos - Apple Developer

Spring API서버에서 Apple 인증(로그인 , 회원가입) 처리하기

Authenticating users with Sign in with Apple | Apple Developer Documentation