[Android] Google Login 구현하기 with Kotlin

akim·2023년 5월 3일
4
post-thumbnail

우리 그럼 로그인은 소셜 로그인으로 해볼까?

말만 들어도 숨이 턱 막힌다!


클라이언트 입장에서는 그냥 자체적으로 일반 로그인을 구현해주면 좋겠지만..
기획자들은 그렇지 않은가보다..
어쩌면 서버 개발자들도 그렇지 않을 것이다..

매번 제대로 맞서지 않고 어찌저찌 구글링으로 잘 피해왔던 과거를 반성하며,
다시는 구글 로그인에 쫄지 않기 위해(?) 최대한 쉽고 간결하게 구글 로그인 구현 과정을 정리해보려고 한다.


기본 요건

  • Android 4.4 이상을 실행하며 Google Play 스토어 또는 Android 4.2.2 이상을 기반으로 하는 Google API 플랫폼을 실행하고 Google Play 서비스 버전 15.0.0 이상을 실행하는 AVD가 있는 에뮬레이터가 포함된 호환 Android 기기.
  • Android 4.4(KitKat) 이상에서 컴파일하도록 구성된 프로젝트

Google Play 서비스 추가

먼저 프로젝트 수준 build.gradle 파일에 GoogleMaven 저장소가 포함되어 있는지 확인한다.

buildscript {
    repositories {
        google()
        mavenCentral()
    }
}

다음으로, 앱 수준 build.gradle 파일에 Google Play 서비스 dependency를 추가한다.

 // Google Play services
  	implementation 'com.google.gms:google-services:4.3.15'
    implementation 'com.google.firebase:firebase-auth:22.0.0'
    implementation 'com.google.firebase:firebase-bom:32.0.0'
    implementation 'com.google.android.gms:play-services-auth:20.5.0'


Google Cloud Console에서 프로젝트 생성 및 OAuth 2.0 클라이언트 ID 생성

1. Google Cloud Console에서 콘솔 프로젝트 생성

아래와 같이 Google Cloud Console에 들어가서 프로젝트 선택을 누른 뒤

오른쪽 상단의 새 프로젝트를 클릭한다.

식별가능한 프로젝트 이름을 적절히 입력해주고 만들기를 클릭힌다. (조직은 없어도 상관없다.)


2. 구글 로그인을 사용할 안드로이드 앱의 패키지명과 SHA-1 해시값 가져오기

패키지명은 안드로이드 스튜디오 왼쪽 사이드바의 트리에서 확인할 수 있다.

위처럼 합쳐져 있을 수도, 아래처럼 펼쳐져 있을 수도 있다. 어떤 형태이든 상관 없으며, 이 경우의 패키지명은 org.joyrooom.carespoon 이 된다.


SHA-1 해시값은 Gradle 코끼리를 누르고 gradle signingReport 를 입력하면 하단 Run 부분에서 확인할 수 있다.


3. OAuth 2.0 클라이언트 ID 가져오기

Firebase Console을 이용해 구글로그인에 필요한 웹 애플리케이션을 자동 생성하고, Client ID를 가져온다.

여기서 얻은 ID는 GoogleSignInOptions 객체를 만들 때 requestIdToken 또는 requestServerAuthCode 메서드에 전달해야 하는 클라이언트 ID이므로, 일단 string.xml 파일에 저장해두도록 한다.



Google 로그인 연동

1. Google 로그인 및 GoogleSignInClient 객체 구성

이제 Google Cloud Console 에서의 준비가 모두 끝났다.

로그인이 이루어지는 activity의 onCreate 메서드(혹은 fragment의 onCreateView 메서드)에서 앱에 필요한 사용자 데이터를 요청하도록 코드를 작성해보자.


전반적인 로직은 아래와 같다.

  1. 사용자의 ID와 기본 프로필 정보를 요청하기 위해 DEFAULT_SIGN_IN 매개변수를 사용하여 GoogleSignInOptions 객체를 만든다.
val googleSignInOption = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)

  1. requestServerAuthCode를 사용하여 앞서 가져온 백엔드 서버의 OAuth 2.0 클라이언트 ID를 넘기고 요청한다.
.requestServerAuthCode(getString(R.string.google_login_client_id))

  1. requestEmail을 사용하면 사용자의 이메일 주소를 요청할 수 있고, requestScopes을 사용하면 Google API에 액세스하기 위해 추가 범위를 요청할 수 있다.
.requestEmail()

  1. 위 과정에서 지정한 옵션들을 이용해 GoogleSignInClient 객체를 만든다. (GoogleSignInOptions 를 넘겨줌)
 private fun getGoogleClient(): GoogleSignInClient {
        val googleSignInOption = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestScopes(Scope("https://www.googleapis.com/auth/pubsub"))
            .requestServerAuthCode(getString(R.string.google_login_client_id)) // string 파일에 저장해둔 client id 를 이용해 server authcode를 요청한다.
            .requestEmail() // 이메일도 요청할 수 있다.
            .build()

        return GoogleSignIn.getClient(requireActivity(), googleSignInOption)
    }

2번에서 넘기는 클라이언트 ID로는 서버측에서 사용자의 이메일과 이름 등을 얻어올 수 있다. 만약 Google API에 액세스하기 위해 여기서 추가적인 정보가 더 필요하다면 accessToken을 넘겨주면 된다.

그러나 최상의 사용자 환경을 제공하려면 처음에 사용자를 로그인 처리할 때 가능한 한 적은 수의 범위를 요청해야 한다. 일반적으로는 GoogleSignInOptions.DEFAULT_SIGN_IN 구성만 있으면 로그인에 충분하기에, requestScopes의 사용은 꼭 필요한지를 잘 따져보고 진행하는 것이 좋을 것이다.


2. 로그인 인텐트를 만들어 구글 로그인 진행

각자의 로그인 화면에서 구글 로그인 브랜드 가이드라인을 이용하여 버튼을 만든다.


registerForActivityResult를 사용해 버튼이 클릭되면 내부에 로그인 인텐트를 만들어 로그인이 진행되도록 한다.

private fun addListener() {
    binding.clGoogleLogin.setOnClickListener { // 버튼 역할을 하는 clGoogleLogin에 클릭리스너를 달아준다.
        requestGoogleLogin()
    }
}

private fun requestGoogleLogin() {
    googleSignInClient.signOut()
    val signInIntent = googleSignInClient.signInIntent
    googleAuthLauncher.launch(signInIntent)
}

완성

로그인 버튼을 클릭하면 구글 로그인을 위한 페이지로 화면이 넘어가고, 모든 정보를 입력하고 동의 절차를 거치고 나면 해당 계정을 통한 로그인을 마칠 수 있다.


전체적인 코드는 아래와 같다. (fragment에서 작업하였다.)

class SignFragment : Fragment() {
    private lateinit var binding: FragmentSignBinding
    private val viewModel: SignViewModel by activityViewModels()
    private val googleSignInClient: GoogleSignInClient by lazy { getGoogleClient() }
    private val googleAuthLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)

            try {
                val account = task.getResult(ApiException::class.java)
                account.idToken?.let { viewModel.requestGiverSignIn(it) } // 서버에 idToken 보내기
                
                // 클라단에서 바로 이름, 이메일 등이 필요하다면 아래와 같이 account를 통해 각 메소드를 불러올 수 있다.
                val userName = account.givenName 
                val serverAuth = account.serverAuthCode
               
                moveSignUpActivity()
               
            } catch (e: ApiException) {
                Log.e(SignFragment::class.java.simpleName, e.stackTraceToString())
            }
        }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentSignBinding.inflate(inflater, container, false)

        addListener()
        return binding.root
    }

    private fun addListener() {
        binding.clGoogleLogin.setOnClickListener {
            requestGoogleLogin()
        }
    }

    private fun requestGoogleLogin() {
        googleSignInClient.signOut()
        val signInIntent = googleSignInClient.signInIntent
        googleAuthLauncher.launch(signInIntent)
    }

    private fun getGoogleClient(): GoogleSignInClient {
        val googleSignInOption = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestScopes(Scope("https://www.googleapis.com/auth/pubsub"))
            .requestServerAuthCode(getString(R.string.google_login_client_id)) // 콘솔에서 가져온 client id 를 이용해 server authcode를 요청한다.
            .requestIdToken(getString(R.string.google_login_client_id)) // 콘솔에서 가져온 client id 를 이용해 id token을 요청한다.
            .requestEmail() // 이메일도 요청할 수 있다.
            .build()

        return GoogleSignIn.getClient(requireActivity(), googleSignInOption)
    }

    private fun moveSignUpActivity() {
        requireActivity().run {
            startActivity(Intent(requireContext(), SignUpActivity::class.java))
            finish()
        }
    }
}

물론.. 아직 리팩토링도 하지 않았고 작업중인 코드라 날것의 코드이니 참고용으로만 보면 될 것 같다.


이제 그 누가 '우리 소셜 로그인으로 작업해볼까요?' 라고 말하더라도 쫄지 말자!

profile
학교 다니는 개발자

4개의 댓글

comment-user-thumbnail
2024년 3월 20일

좋은 글 감사합니다 ! 로그인 화면 창 뜨는 것 까진 되었는데, 로그인 한 계정 이름, 이메일 정보 불러오려니 오류가 나네요ㅠㅠ.

1개의 답글
comment-user-thumbnail
약 12시간 전

혹시 token을 요청하기 전에는 괜찮았는데 requestIdToken을 추가하니까 자꾸 com.google.android.gms.common.api.ApiException: 10 에러가 나는데
3. OAuth 2.0 클라이언트 ID 가져오기
여기서 조금 차이가 나는 것 같은데 웹 어플리케이션으로 만들어야 하는 건가요? Firebase로 하면 쉽게 만들 수 있나요...?!

1개의 답글