Google OAuth로 access token 가져오기

ChoiUS·2025년 8월 24일
0

Android

목록 보기
6/6

access token 전달?

원래는 id token을 서버에 전달하고 있었는데, 회원탈퇴 로직을 정하는 과정에서 id token 대신 access token을 전달하기로 했다.
보안상 좋지 않지만, 시간이 많지 않아서 지금 작성된 로직을 최소한으로 변경해도 되는 방법을 채택했다.


실제 코드

라이브러리는 이전에 사용했던 것들을 그대로 사용했다.
UI는 Compose를 기준으로 작성되었지만, remember를 제외하면 크게 다를 것 같진 않다.

    @Composable
    fun rememberGoogleSignInLauncherWithAccount(
        onSuccess: (googleAccount: GoogleSignInAccount, accessToken: String) -> Unit,
        onError: (message: String, exception: Exception?) -> Unit
    ): ActivityResultLauncher<Intent> {
        val context = LocalContext.current
        val coroutineScope = rememberCoroutineScope()

        return rememberLauncherForActivityResult(
            contract = ActivityResultContracts.StartActivityForResult()
        ) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
                try {
                    val account = task.result
                    Log.d("GoogleLogin", "로그인 성공: ${account.email}")

                    coroutineScope.launch {
                        try {
                            val accessToken = withContext(Dispatchers.IO) {
                                GoogleAuthUtil.getToken(
                                    context,
                                    account.account!!,
                                    "oauth2:${Scope("https://www.googleapis.com/auth/userinfo.profile").scopeUri}"
                                )
                            }
                            Log.d("GoogleLogin", "액세스 토큰 수신 성공: $accessToken")
                            onSuccess(account, accessToken)
                        } catch (e: Exception) {
                            Log.e("GoogleLogin", "액세스 토큰 요청 실패", e)
                            onError("액세스 토큰 요청 실패", e)
                        }
                    }
                } catch (e: Exception) {
                    Log.e("GoogleLogin", "로그인 결과 처리 실패", e)
                    onError("로그인 결과 처리 실패", e)
                }
            } else {
                Log.w("GoogleLogin", "로그인 결과가 OK가 아닙니다. resultCode: ${result.resultCode}")
                onError("로그인 취소됨 (결과 코드: ${result.resultCode})", null)
            }
        }
    }

if-else와 try-catch가 여러개 겹쳐있어서 더럽게 작성되긴 했지만 일단 빠르게 넘어가기로 했다.
GoogleSignInAccountGoogleSignIn이 deprecated 됐다고 나오지만, 이것도 무시해주기로 했다...


val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(WEB_CLIENT_ID)
        .requestServerAuthCode(WEB_CLIENT_ID)
        .requestEmail()
        .build()

google client를 사용하기 위해 빌더도 추가한다.
id token을 가져오는 로그인과 마찬가지로 웹 클라이언트 용 아이디를 추가해야 한다.


    val googleSignInClient = remember { GoogleSignIn.getClient(context, GoogleAccountUtil.gso) }
    val googleSignInLauncher = GoogleAccountUtil.rememberGoogleSignInLauncherWithAccount(
        onSuccess = { 
        	//TODO: 로그인 성공 콜백
        },
        onError = { message, exception ->
           //TODO: 로그인 실패 콜백
        }
    )
    
    
    val signInIntent = googleSignInClient.signInIntent
    googleSignInLauncher.launch(signInIntent)

이제 launcher와 intent를 가져와서 실행해주면 된다.
이번에는 MVI를 사용한 프로젝트를 진행해서, 각 콜백에 ui event를 호출하도록 했다.


GoogleAuthUtil.clearToken(context, socialAccessToken)

서버에서 탈퇴가 성공하면, 토큰을 만료시키도록 했다.



트러블슈팅

회원탈퇴를 성공적으로 마쳤는데, 다시 회원가입 한 뒤 탈퇴를 할 때 탈퇴가 실패했다.

// 서버에 기록된 로그

HTTP Status: 400 BAD_REQUEST, Body: {
  "error": "invalid_token"
}
c.s.d.a.o.google.client.GoogleClient     : 구글 토큰 폐기 중 예상치 못한 오류 발생: 구글 연동 해제에 실패했습니다.

또 신기했던 건 시간이 지난 후 다시 탈퇴를 시도할 때는 성공한다는 것이었다.

조금 찾아보니 액세스 토큰은 계정마다 발급되는 것이고, 해당 서비스를 위해 발급된 것이 아니기 때문에 토큰 자체는 탈퇴했다고 사라지지 않았다.
그래서 탈퇴를 다시 할 때 문제가 발생했고, 시간이 지난 후에는 토큰이 만료되어 다시 발급받았기 때문에 성공했던 것이다.

GoogleAuthUtil.clearToken(context, socialAccessToken)

해당 코드로 발급받은 토큰을 회원탈퇴 한 뒤에 만료시켜서 해결했다.
저 토큰 만료가 안 되는 걸 몰라서 팀원들이 며칠 동안 골머리를 앓았는데, 해결했을 때는 조금 뿌듯했다.
그래도 다음에는 보안을 위해 access token을 전달하는 방법 말고 다른 방법을 사용해보도록 하자.

profile
사람을 위한 개발자

0개의 댓글