Google OAuth를 사용하여 Android에서 보안 로그인 만들기

PubNub Developer Relations - Mar 11 - - Dev Community

2022년 1월 현재 45억 5천만 명이 소셜 미디어 프로필을 하나 이상 보유하고 있는 것으로 추산되는 가운데, 대부분의 애플리케이션이 사용자가 쉽게 참여할 수 있도록 일종의 소셜 로그인 기능을 제공하는 것은 당연한 일입니다. 이는 사용자 경험을 향상시키는 데 있어 핵심적인 요소입니다. 개발 관점에서 보면 Facebook, Twitter, Google과 같은 모든 소셜 로그인 제공업체에는 큰 공통점이 있습니다. 그 공통의 퍼즐 조각이 바로 OAuth, 더 구체적으로는 OAuth 2.0입니다. 하지만 단순히 OAuth를 사용하는 것만이 능사가 아니며, 최적의 구현을 위해서는 범위, OAuth 클라이언트 ID 및 OAuth 동의 화면에 대한 정확한 이해가 필요합니다.

인증과 권한 부여의 차이점은 무엇인가요?

인증 (authn)은 사용자를 식별하거나 확인하는 프로세스를 말하며, 여기서는 Google OAuth를 통해 수행할 수 있습니다. 반면 권한 부여(authz)는 인증된 사용자가 특정 리소스에 액세스하는 데 필요한 _권한_을 가지고 있는지 여부와 관련이 있습니다. 여기서 범위가 중요한 역할을 하는데, OAuth에서 권한을 정의하기 때문입니다.

authn과 authz는 모두 밀접한 관련이 있으며 일반적으로 OAuth 2.0 프로세스 내에서 찾을 수 있습니다. 프로세스의 두 부분 모두에서 클라이언트 ID와 클라이언트의 비밀을 유지하기 위한 서버가 필요하며, 이 서버는 Google Cloud를 사용하여 관리할 수 있습니다.

Authn과 Authz가 OAuth에 적합한 방법

OAuth는 엄밀히 말해 인증의 표준이며, 개발자들 사이에서 가장 큰 오해는 Authn(인증)이 그 일부라고 생각하는 것입니다. OAuth 2.0은 특히 Google과 같은 소셜 플랫폼에서 네이티브 기술, Javascript 또는 Kotlin의 조합을 사용하여 프로세스가 연속적으로 실행되는 경우 authn도 통합합니다.

다음은 프로세스에 대한 개요입니다:

보시다시피 두 가지 주요 구성 요소가 있습니다. 먼저 사용자로부터 필요한 권한이 필요합니다. 이는 사용자가 로그인한 경우 발생하며, 많은 API가 openid를 사용하여 OAuth의 인증 부분을 연결합니다. 사용자 계정 권한으로부터 권한을 부여받으면 리소스 소유자와 통신을 시작할 수 있습니다. 권한 부여를 인증 서버로 보내면 액세스 토큰을 다시 받습니다. 이제 액세스 토큰을 사용하여 사용자와 관련된 필요한 정보에 액세스할 수 있습니다.

OAuth 2.0을 구현할 때는 이러한 매개변수에서 사소한 실수라도 발생하면 authn 및 authz 프로세스가 실패할 수 있으므로 올바른 리디렉션 URI와 콜백을 설정하는 것이 매우 중요합니다. 또한 보안 및 사용자 경험 개선의 일환으로 Google은 OAuth 2.0용 크롬 확장 프로그램을 제공합니다.

Android에서 Google 로그인으로 인증하기

Android용 애플리케이션 인증, 특히 Google 로그인을 통한 인증은 일반적인 보안 조치입니다. 하지만 이는 단순히 Android 애플리케이션에 Google을 추가하는 것에서 끝나는 것이 아니라 OAuth를 이해한 다음 SDK와 Google API를 살펴보는 것에서 시작됩니다.

Google 로그인을 시작하기 위해 서버 역할을 하는 PubNub의 기능인 함수를 사용하겠습니다. 이러한 함수를 사용하면 OAuth 프로세스에서 알림을 더 간단하게 구현할 수 있습니다. OAuth 프로세스에 대한 코드 조각은 GitHub에서도 찾을 수 있습니다.

또한 Google API 계정에 올바른 패키지 이름이 설정되어 있는지, OAuth 클라이언트 ID와 OAuth 동의 화면이 구성되어 있는지 확인하세요.

다음으로 로그인 프로세스를 시작할 수 있도록 Android 애플리케이션에 Google을 추가하겠습니다. 먼저 프로젝트 수준의 Gradle 파일에 { google() }저장소가 있는지 확인합니다. 이렇게 하면 애플리케이션이 Google SDK를 활용할 수 있습니다.

그런 다음 앱 수준의 Gradle 파일로 이동하여 Google Play 서비스 인증 종속성을 업데이트합니다. 2022년 1월 현재 가장 최신 버전은 com.google.android.gms:play-services-auth:19.2.0입니다.

    implementation 'com.google.android.gms:play-services-auth:19.2.0'
Enter fullscreen mode Exit fullscreen mode

이제 프로젝트에 업데이트된 종속성이 추가되었지만 아직 로그인 프로세스를 진행할 수 없습니다. Android 매니페스트 파일을 편집하여 Firebase와 관련된 인터넷 권한을 추가해야 합니다:

    <uses-permission android:name="android.permission.INTERNET" />
Enter fullscreen mode Exit fullscreen mode

구글 로그인 버튼 추가하기

이 단계는 매우 간단합니다. activity_main.xml 파일에 로그인 컴포넌트를 추가합니다. 로그인 버튼의 스타일은 Google에서 지정하지만 만일을 대비하여 Google의 브랜드 지침을 확인하세요. 데스크톱 앱에서는 우수한 사용자 경험이 핵심이며, 여기에는 로그인 버튼에 사용되는 스타일과 식별자가 포함됩니다.

    <com.google.android.gms.common.SignInButton
                android:id="@+id/sign_in_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent" android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
                android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp"
                app:layout_constraintVertical_bias="0.665"
    />
Enter fullscreen mode Exit fullscreen mode

Android 앱에서는 Google 로그인으로 인증하는 것이 일반적인 보안 조치입니다. 여기에서는 이 프로세스가 어떻게 작동하는지에 대한 예를 MainActivity에서 볼 수 있습니다. 이 스니펫은 Kotlin을 사용하여 Google 로그인을 구현하기 위한 가이드 역할을 합니다.

class MainActivity : AppCompatActivity() {
    private val RC_SIGN_IN = 9001
    private var mGoogleSignInClient: GoogleSignInClient? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.server_client_id))
            .requestEmail()
            .build()
        mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
    }
    public override fun onStart() {
        super.onStart()
        val mGmailSignIn = findViewById<SignInButton>(R.id.sign_in_button)
        val account = GoogleSignIn.getLastSignedInAccount(this)
        Log.w("Sign In: ", account.toString())
        mGmailSignIn.setOnClickListener {
                signIn()
        }
    }
    public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == RC_SIGN_IN) {
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            handleSignInResult(task)
        }
    }
    private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
        Log.w("Sign In: ", completedTask.toString())
        try {
            val account = completedTask.getResult(ApiException::class.java)
            val idToken = account!!.idToken
            Log.w("Sign In: ", idToken.toString())
            authCheck(idToken.toString())
        } catch (e: ApiException) {
            Log.w("Sign In: ", "signInResult:failed code=" + e.statusCode)
        }
    }
    private fun signIn() {
        val signInIntent = mGoogleSignInClient!!.signInIntent
        startActivityForResult(signInIntent, RC_SIGN_IN)
    }
    private fun authCheck(token: String) {
        // Instantiate the RequestQueue.
        val queue = newRequestQueue(this)
        val url = "https://pubsub.pubnub.com/v1/blocks/sub-key/sub-c-87dbd99c-e470-11e8-8d80-3ee0fe19ec50/google?token=$token"
        // Request a string response from the provided URL.
        val stringRequest = StringRequest(
            Request.Method.GET, url,
            Response.Listener<String> { response ->
                // Display the first 500 characters of the response string.
                Log.d("Response: ", response)
            },
            Response.ErrorListener {Log.d("Response: ", "Didn't Work!")})
        // Add the request to the RequestQueue.
        queue.add(stringRequest)
    }
}
Enter fullscreen mode Exit fullscreen mode

위의 코드를 사용하면 이제 앱에서 사용자를 인증하고 확인할 수 있습니다! 인증에 성공하면 다음 단계는 액세스 토큰을 검색하는 것입니다. 이 프로세스는 이전 프로세스와 매우 유사하지만 이번에는 토큰 URL( https://www.googleapis.com/oauth2/v4/token)에 HTTP POST 요청을 제출합니다. 이 작업은 JavaScript를 사용하여 수행할 수 있으며 코드는 GitHub에서 참조할 수 있습니다.

PubNub 함수는 다음과 비슷할 수 있습니다:

export default (request, response) => {
    const pubnub = require('pubnub');
    const xhr = require('xhr');
    let headersObject = request.headers;
    let paramsObject = request.params;
    let methodString = request.method;
    let bodyString = request.body;
    // Set the status code - by default it would return 200
    response.status = 200;
    // Set the headers the way you like

    var client_id = "your google client id"
    var client_secret = "your google client secret";
    var redirect = "https://pubsub.pubnub.com/v1/blocks/sub-key/sub-c-87dbd99c-e470-11e8-8d80-3ee0fe19ec50/Auth";

    response.headers['X-Custom-Header'] = 'CustomHeaderValue';
    var dataString = `client_id=${client_id}&client_secret=${client_secret}&redirect_uri=${redirect}&grant_type=authorization_code&code=CODE`;
    var url = `https://www.googleapis.com/oauth2/v4/token${dataString}`;
    var options = {
        "method": 'POST',
        "body": dataString,
    };

    console.log(dataString);
    return xhr.fetch(url, options).then((x) => {
        const body = JSON.parse(x.body);
        console.log(body);
         return response.send(body);
    }).catch((err) => {
        // console.log(err)
        return response.send("Malformed JSON body.");
    });
};
Enter fullscreen mode Exit fullscreen mode

Android 최종 사용자로부터 인증 코드를 받아야 하며, 이 코드를 MainActivity.kt 파일에 추가하여 인증 코드를 획득할 수 있습니다:

Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
try {
    GoogleSignInAccount account = task.getResult(ApiException.class);
    String authCode = account.getServerAuthCode();
    // Show signed-un UI
    updateUI(account);
    // TODO(developer): send code to server and exchange for access/refresh/ID tokens
} catch (ApiException e) {
    Log.w(TAG, "Sign-in failed", e);
    updateUI(null);
}
Enter fullscreen mode Exit fullscreen mode

이렇게 하면 안드로이드 앱에서 OAuth 2.0을 사용하여 Google 로그인을 구현하여 사용자 환경을 원활하게 유지하고 앱을 안전하게 보호할 수 있습니다.

개방형 인터넷에서 애플리케이션을 테스트한 후에는 문제 없이 전체 가입 프로세스를 진행할 수 있어야 합니다. 이 프로세스에는 앱에 설정할 수 있는 다양한 범위 또는 권한과 함께 제공되는 인증용 Google OAuth가 포함될 수 있습니다. 사용자 경험은 매끄러워야 한다는 점을 기억하세요.

이제 모든 단계를 살펴봤으니 OAuth 클라이언트 ID 기반 인증과 권한 부여의 차이점을 더 잘 이해하셨을 것입니다. 이 지식을 바탕으로 사용자가 OAuth 동의 화면을 통해 로그인한 후 수행할 수 있는 작업을 살펴볼 수 있습니다.

클라이언트 측 SDK의 패키지 이름은 JavaScript, Kotlin 또는 다른 플랫폼의 네이티브 등 Google Cloud 프로젝트에 등록된 이름과 일치해야 합니다. 이 페이지에서 액세스할 수 없는 외부 콘텐츠가 있는 경우 https://pubsub-quickstart-app.pubnub.com/signup 에서도 볼 수 있습니다. 이 페이지에서는 OAuth에 필요한 리디렉션 URI 및 콜백 매개 변수의 올바른 사용법을 보여줍니다.

PubNub의 도움이 필요하신가요?

저희 플랫폼은 개발자에게 웹 앱, 모바일 앱, 데스크톱 앱 및 IoT 디바이스를 위한 실시간 상호작용을 구축, 제공 및 관리할 수 있는 리소스를 제공합니다. 알림을 통해 사용자의 참여를 유도하거나 고급 상호 작용을 위한 고유 식별자를 사용하여 비공개 Firebase 애플리케이션을 만들 수 있습니다.

저희 플랫폼의 백본은 업계에서 가장 크고 확장성이 뛰어난 실시간 에지 메시징 네트워크입니다. Google 클라우드 서비스로 구동되고 Chrome과 통합되어 추가 기능을 제공하는 이 플랫폼은 전 세계 15개 이상의 PoP를 갖추고 월간 8억 명의 활성 사용자를 지원하며 99.999%의 안정성을 갖추고 있어 트래픽 폭증으로 인한 중단, 동시성 제한, 대기 시간 문제를 걱정할 필요가 없습니다.

PubNub를 시작하는 방법

라이브 투어를 확인하여 5분 이내에 모든 PubNub 기반 앱의 핵심 개념을 익혀보세요. OpenID는 이러한 개념의 중요한 부분이 될 수 있습니다. PubNub 계정에 가입하면 PubNub 키에 무료로 즉시 액세스할 수 있습니다. PubNub 문서는 사용 사례나 선호하는 SDK에 관계없이 코드 스니펫 또는 세부 매개변수가 포함된 "스니펫"을 포함하여 설치 및 설정 프로세스를 안내합니다.

내용

인증과 권한 부여의 차이점은 무엇인가요?Authn과 Authz가 OAuth에 어떻게적용되는가안드로이드에서 구글 로그인으로인증하기Google 로그인 버튼추가하기펍넙의 도움이 필요한경우펍넙으로 시작하는 방법

펍넙이 어떤 도움을 줄 수 있나요?

이 문서는 원래 PubNub.com에 게시되었습니다.

저희 플랫폼은 개발자가 웹 앱, 모바일 앱 및 IoT 디바이스를 위한 실시간 인터랙티브를 구축, 제공 및 관리할 수 있도록 지원합니다.

저희 플랫폼의 기반은 업계에서 가장 크고 확장성이 뛰어난 실시간 에지 메시징 네트워크입니다. 전 세계 15개 이상의 PoP가 월간 8억 명의 활성 사용자를 지원하고 99.999%의 안정성을 제공하므로 중단, 동시 접속자 수 제한 또는 트래픽 폭증으로 인한 지연 문제를 걱정할 필요가 없습니다.

PubNub 체험하기

라이브 투어를 통해 5분 이내에 모든 PubNub 기반 앱의 필수 개념을 이해하세요.

설정하기

PubNub 계정에 가입하여 PubNub 키에 무료로 즉시 액세스하세요.

시작하기

사용 사례나 SDK에 관계없이 PubNub 문서를 통해 바로 시작하고 실행할 수 있습니다.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .