우리 그럼 로그인은 소셜 로그인으로 해볼까?
말만 들어도 숨이 턱 막힌다!
클라이언트 입장에서는 그냥 자체적으로 일반 로그인을 구현해주면 좋겠지만..
기획자들은 그렇지 않은가보다..
어쩌면 서버 개발자들도 그렇지 않을 것이다..
매번 제대로 맞서지 않고 어찌저찌 구글링으로 잘 피해왔던 과거를 반성하며,
다시는 구글 로그인에 쫄지 않기 위해(?) 최대한 쉽고 간결하게 구글 로그인 구현 과정을 정리해보려고 한다.
먼저 프로젝트 수준 build.gradle
파일에 Google
의 Maven
저장소가 포함되어 있는지 확인한다.
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에 들어가서 프로젝트 선택을 누른 뒤
오른쪽 상단의 새 프로젝트를 클릭한다.
식별가능한 프로젝트 이름을 적절히 입력해주고 만들기를 클릭힌다. (조직은 없어도 상관없다.)
패키지명은 안드로이드 스튜디오 왼쪽 사이드바의 트리에서 확인할 수 있다.
위처럼 합쳐져 있을 수도, 아래처럼 펼쳐져 있을 수도 있다. 어떤 형태이든 상관 없으며, 이 경우의 패키지명은 org.joyrooom.carespoon
이 된다.
SHA-1 해시값은 Gradle 코끼리를 누르고 gradle signingReport
를 입력하면 하단 Run 부분에서 확인할 수 있다.
Firebase Console을 이용해 구글로그인에 필요한 웹 애플리케이션을 자동 생성하고, Client ID를 가져온다.
여기서 얻은 ID는 GoogleSignInOptions
객체를 만들 때 requestIdToken
또는 requestServerAuthCode
메서드에 전달해야 하는 클라이언트 ID이므로, 일단 string.xml
파일에 저장해두도록 한다.
이제 Google Cloud Console 에서의 준비가 모두 끝났다.
로그인이 이루어지는 activity의 onCreate
메서드(혹은 fragment의 onCreateView
메서드)에서 앱에 필요한 사용자 데이터를 요청하도록 코드를 작성해보자.
전반적인 로직은 아래와 같다.
DEFAULT_SIGN_IN
매개변수를 사용하여 GoogleSignInOptions
객체를 만든다.val googleSignInOption = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
requestServerAuthCode
를 사용하여 앞서 가져온 백엔드 서버의 OAuth 2.0 클라이언트 ID를 넘기고 요청한다..requestServerAuthCode(getString(R.string.google_login_client_id))
requestEmail
을 사용하면 사용자의 이메일 주소를 요청할 수 있고, requestScopes
을 사용하면 Google API에 액세스하기 위해 추가 범위를 요청할 수 있다..requestEmail()
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
의 사용은 꼭 필요한지를 잘 따져보고 진행하는 것이 좋을 것이다.
각자의 로그인 화면에서 구글 로그인 브랜드 가이드라인을 이용하여 버튼을 만든다.
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()
}
}
}
물론.. 아직 리팩토링도 하지 않았고 작업중인 코드라 날것의 코드이니 참고용으로만 보면 될 것 같다.
이제 그 누가 '우리 소셜 로그인으로 작업해볼까요?' 라고 말하더라도 쫄지 말자!
혹시 token을 요청하기 전에는 괜찮았는데 requestIdToken을 추가하니까 자꾸 com.google.android.gms.common.api.ApiException: 10 에러가 나는데
3. OAuth 2.0 클라이언트 ID 가져오기
여기서 조금 차이가 나는 것 같은데 웹 어플리케이션으로 만들어야 하는 건가요? Firebase로 하면 쉽게 만들 수 있나요...?!
좋은 글 감사합니다 ! 로그인 화면 창 뜨는 것 까진 되었는데, 로그인 한 계정 이름, 이메일 정보 불러오려니 오류가 나네요ㅠㅠ.