App Engine에서 사용자 로그인


이 튜토리얼에서는 Identity Platform, App Engine 표준 환경, Datastore를 사용하여 타사 사용자 인증 정보를 검색, 확인, 저장하는 방법을 보여줍니다.

이 문서는 사용자의 개인 메모장에 사용자 메모를 저장하는 Firenotes라는 간단한 메모 작성 애플리케이션을 안내합니다. 메모장은 사용자별로 저장되며 각 사용자의 고유한 Identity Platform ID로 식별됩니다. 이 애플리케이션에는 다음과 같은 구성 요소가 있습니다.

  • 프런트엔드는 로그인 사용자 인터페이스를 구성하고 Identity Platform ID를 검색합니다. 또한 인증 상태 변경을 처리하고 사용자가 메모를 볼 수 있게 해줍니다.

  • FirebaseUI는 인증 및 UI 작업을 간소화하는 오픈소스 삽입형 솔루션입니다. SDK는 사용자 로그인을 처리하고 여러 공급업체를 하나의 계정에 연결하며 비밀번호를 복구하는 등의 작업을 처리합니다. 또한 원활하고 안전한 로그인 환경을 위한 인증 권장사항을 구현합니다.

  • 백엔드는 사용자의 인증 상태를 확인하고 사용자의 프로필 정보 및 메모를 반환합니다.

이 애플리케이션은 NDB 클라이언트 라이브러리를 사용하여 Datastore에 사용자 인증 정보를 저장하지만, 필요에 따라 다른 데이터베이스에 사용자 인증 정보를 저장할 수 있습니다.

Firenotes는 Flask 웹 애플리케이션 프레임워크를 기반으로 합니다. 샘플 앱에서는 단순성과 편의성으로 인해 Flask를 사용하지만, 여기에서 설명하는 개념과 기술은 어떠한 프레임워크에도 적용될 수 있습니다.

목표

이 튜토리얼을 완료하면 다음을 수행할 수 있습니다.

  • Identity Platform용 FirebaseUI로 사용자 인터페이스를 구성합니다.
  • Identity Platform ID 토큰을 획득하고 서버 측 인증을 사용하여 확인합니다.
  • Datastore에 사용자 인증 정보 및 관련 데이터를 저장합니다.
  • NDB 클라이언트 라이브러리를 사용하여 데이터베이스를 쿼리합니다.
  • App Engine에 앱을 배포합니다.

비용

이 튜토리얼에서는 다음과 같은 비용이 청구될 수 있는 Google Cloud 구성요소를 사용합니다.

  • Datastore
  • Identity Platform

가격 계산기를 사용하면 예상 사용량을 기준으로 예상 비용을 산출할 수 있습니다. Google Cloud를 처음 사용하는 사용자는 무료 체험판을 사용할 수 있습니다.

시작하기 전에

  1. Git, Python 2.7, virtualenv를 설치합니다. 최신 버전의 Python을 설치하는 등의 Python 개발 환경 설정에 관한 자세한 내용은 Google Cloud용 Python 개발 환경 설정을 참조하세요.
  2. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  3. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  4. Install the Google Cloud CLI.
  5. To initialize the gcloud CLI, run the following command:

    gcloud init
  6. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  7. Install the Google Cloud CLI.
  8. To initialize the gcloud CLI, run the following command:

    gcloud init

SDK를 이미 다른 프로젝트에 설치하고 초기화한 경우 gcloud 프로젝트를 Firenotes용으로 사용 중인 App Engine 프로젝트 ID에 추가합니다. gcloud 도구로 프로젝트를 업데이트하는 구체적인 명령어는 Google Cloud SDK 구성 관리를 참조하세요.

샘플 앱 복제

로컬 머신에 샘플을 다운로드하는 방법은 다음과 같습니다.

  1. 샘플 애플리케이션 저장소를 로컬 머신에 클론합니다.

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    또는 zip 파일로 샘플을 다운로드하고 압축을 풀 수 있습니다.

  2. 샘플 코드가 있는 디렉터리로 이동합니다.

    cd python-docs-samples/appengine/standard/firebase/firenotes
    

사용자 인터페이스 추가

Identity Platform용 FirebaseUI를 구성하고 ID 공급업체를 사용 설정하는 방법은 다음과 같습니다.

  1. 다음 단계에 따라 앱에 Identity Platform을 추가합니다.

    1. Google Cloud 콘솔로 이동합니다.
      Google Cloud 콘솔로 이동
    2. 사용할 Google Cloud 프로젝트를 선택합니다.
      • 기존 프로젝트가 있으면 페이지 상단의 조직 선택 드롭다운 목록에서 프로젝트를 선택합니다.
      • 기존 Google Cloud 프로젝트가 없으면 Google Cloud 콘솔에서 새 프로젝트를 만듭니다.
    3. Google Cloud 콘솔에서 Identity Platform Marketplace 페이지로 이동합니다.
      Identity Platform Marketplace 페이지로 이동
    4. Identity Platform Marketplace 페이지에서 고객 ID 사용 설정을 클릭합니다.
    5. Google Cloud 콘솔에서 고객 ID 사용자 페이지로 이동합니다.
      사용자 페이지로 이동
    6. 오른쪽 상단에서 애플리케이션 설정 세부정보를 클릭합니다.
    7. 애플리케이션 설정 세부정보를 웹 애플리케이션에 복사합니다.

      // Obtain the following from the "Add Firebase to your web app" dialogue
      // Initialize Firebase
      var config = {
        apiKey: "<API_KEY>",
        authDomain: "<PROJECT_ID>.firebaseapp.com",
        databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
        projectId: "<PROJECT_ID>",
        storageBucket: "<BUCKET>.appspot.com",
        messagingSenderId: "<MESSAGING_SENDER_ID>"
      };
  2. backend/app.yaml 파일을 수정하고 환경 변수에 Google Cloud 프로젝트 ID를 입력합니다.

    # Copyright 2021 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #      http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    runtime: python27
    api_version: 1
    threadsafe: true
    service: backend
    
    handlers:
    - url: /.*
      script: main.app
    
    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : 'true'
    
  3. frontend/main.js 파일에서 사용자에게 제공할 제공 업체를 선택하여 FirebaseUI 로그인 위젯을 구성합니다.

    // Firebase log-in widget
    function configureFirebaseLoginWidget() {
      var uiConfig = {
        'signInSuccessUrl': '/',
        'signInOptions': [
          // Leave the lines as is for the providers you want to offer your users.
          firebase.auth.GoogleAuthProvider.PROVIDER_ID,
          firebase.auth.FacebookAuthProvider.PROVIDER_ID,
          firebase.auth.TwitterAuthProvider.PROVIDER_ID,
          firebase.auth.GithubAuthProvider.PROVIDER_ID,
          firebase.auth.EmailAuthProvider.PROVIDER_ID
        ],
        // Terms of service url
        'tosUrl': '<your-tos-url>',
      };
    
      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      ui.start('#firebaseui-auth-container', uiConfig);
    }
  4. Google Cloud Console에서 보존할 공급업체를 사용 설정합니다.

    1. Google Cloud 콘솔에서 고객 ID 공급업체 페이지로 이동합니다.
      공급업체 페이지로 이동
    2. 공급업체 추가를 클릭합니다.
    3. 공급업체 선택 드롭다운 목록에서 사용할 공급업체를 선택합니다.
    4. 사용 설정됨 옆에 있는 버튼을 클릭하여 공급업체를 사용 설정합니다.
      • 타사 ID 공급업체의 경우 공급업체의 개발자 사이트에서 확인한 공급업체 ID와 보안 비밀을 입력합니다. 구체적인 안내는 Firebase 문서 중에서 Facebook, Twitter, GitHub 가이드의 '시작하기 전에' 섹션을 참조하세요.
      • SAML 및 OIDC 통합의 경우 IdP의 구성을 참조하세요.
  5. Identity Platform의 승인된 도메인 목록에 도메인을 추가합니다.

    1. Google Cloud 콘솔에서 고객 ID 설정 페이지로 이동합니다.
      설정 페이지로 이동
    2. 승인된 도메인에서 도메인 추가를 클릭합니다.
    3. 앱의 도메인을 다음 형식으로 입력합니다.

      [PROJECT_ID].appspot.com
      

      도메인 이름 앞에 http://를 포함하지 마세요.

종속 항목 설치

  1. backend 디렉터리로 이동하여 애플리케이션 설정을 완료합니다.

    cd backend/
    
  2. 프로젝트의 lib 디렉터리에 종속 항목을 설치합니다.

    pip install -t lib -r requirements.txt
    
  3. appengine_config.py에서 vendor.add() 메서드는 lib 디렉터리에 라이브러리를 등록합니다.

애플리케이션을 로컬로 실행

애플리케이션을 로컬로 실행하려면 App Engine 로컬 개발 서버를 사용합니다.

  1. 다음 URL을 main.jsbackendHostURL로 추가합니다.

    http://localhost:8081

  2. 애플리케이션의 루트 디렉터리로 이동합니다. 그런 다음 개발 서버를 시작합니다.

    dev_appserver.py frontend/app.yaml backend/app.yaml
    
  3. 웹브라우저에서 http://localhost:8080/을 방문합니다.

서버에서 사용자 인증

이제 프로젝트를 설정했고 개발을 위해 애플리케이션을 초기화했으므로, 코드를 살펴보면서 서버에서 Identity Platform ID 토큰을 검색하고 확인하는 방법을 알아보겠습니다.

Identity Platform에서 ID 토큰 가져오기

서버 측 인증의 첫 번째 단계는 확인할 액세스 토큰을 검색하는 것입니다. 인증 요청은 Identity Platform의 onAuthStateChanged() 리스너로 처리됩니다.

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    $('#logged-out').hide();
    var name = user.displayName;

    /* If the provider gives a display name, use the name for the
    personal welcome message. Otherwise, use the user's email. */
    var welcomeName = name ? name : user.email;

    user.getIdToken().then(function(idToken) {
      userIdToken = idToken;

      /* Now that the user is authenicated, fetch the notes. */
      fetchNotes();

      $('#user').text(welcomeName);
      $('#logged-in').show();

    });

  } else {
    $('#logged-in').hide();
    $('#logged-out').show();

  }
});

사용자가 로그인하면 콜백의 Identity Platform getToken() 메서드가 JSON 웹 토큰(JWT) 형식으로 Identity Platform ID 토큰을 반환합니다.

서버에서 토큰 확인

사용자가 로그인한 후 프런트엔드 서비스는 AJAX GET 요청을 통해 사용자의 메모장에 있는 기존 메모장을 가져옵니다. 이 때 사용자의 데이터에 액세스하려면 승인이 필요하므로 Bearer 스키마를 사용하여 요청의 Authorization 헤더를 통해 JWT를 전송합니다.

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  })

클라이언트가 서버 데이터에 액세스하려면 먼저 토큰이 Identity Platform에 의해 서명되었는지 여부를 서버가 확인해야 합니다. Python용 Google 인증 라이브러리를 사용하여 이 토큰을 확인할 수 있습니다. 인증 라이브러리의 verify_firebase_token 함수를 사용하여 Bearer 토큰을 확인하고 클레임을 추출합니다.

id_token = request.headers["Authorization"].split(" ").pop()
claims = google.oauth2.id_token.verify_firebase_token(
    id_token, HTTP_REQUEST, audience=os.environ.get("GOOGLE_CLOUD_PROJECT")
)
if not claims:
    return "Unauthorized", 401

각 ID 공급업체는 서로 다른 클레임 집합을 전송하지만 각각의 클레임에는 순 사용자 ID를 가진 sub 클레임 및 name 또는 email와 같이 앱에서 사용자 환경을 맞춤설정할 수 있는 일부 프로필 정보를 제공하는 클레임을 적어도 하나씩 가지고 있습니다.

Datastore에서 사용자 데이터 관리

사용자를 인증한 후에는 로그인 세션이 끝나도 사용자의 데이터가 유지되도록 데이터를 저장해야 합니다. 다음 섹션에서는 메모를 Datastore 항목으로 저장하고 사용자 ID에 따라 항목을 분리하는 방법에 대해 설명합니다.

사용자 데이터를 저장하는 항목 만들기

정수 또는 문자열과 같은 특정 속성을 가지고 있는 NDB 모델 클래스를 선언하여 Datastore에서 항목을 만들 수 있습니다. Datastore는 종류별로 항목의 색인을 생성합니다. Firenotes의 경우 각 항목의 종류는 Note입니다. 쿼리용으로 각 Note키 이름과 함께 저장되며, 키 이름은 이전 섹션의 sub 클레임에서 가져온 사용자 ID입니다.

다음 코드에서는 항목의 속성을 설정하기 위해 항목을 만들 때 모델 클래스의 생성자 메서드를 사용하는 방법과 생성 후 개별 속성을 할당하는 방법을 모두 보여줍니다.

data = request.get_json()

# Populates note properties according to the model,
# with the user ID as the key name.
note = Note(parent=ndb.Key(Note, claims["sub"]), message=data["message"])

# Some providers do not provide one of these so either can be used.
note.friendly_id = claims.get("name", claims.get("email", "Unknown"))

새로 생성된 Note를 Datastore에 기록하려면 note 객체에 대해 put() 메서드를 호출합니다.

사용자 데이터 검색

특정 사용자 ID와 연관된 사용자 데이터를 검색하려면 NDB query() 메서드를 사용하여 동일한 항목 그룹에서 메모용 데이터베이스를 검색합니다. 같은 그룹, 즉 동일한 상위 경로에 속한 항목은 공통 키 이름(여기서는 사용자 ID)을 공유합니다.

def query_database(user_id):
    """Fetches all notes associated with user_id.

    Notes are ordered them by date created, with most recent note added
    first.
    """
    ancestor_key = ndb.Key(Note, user_id)
    query = Note.query(ancestor=ancestor_key).order(-Note.created)
    notes = query.fetch()

    note_messages = []

    for note in notes:
        note_messages.append(
            {
                "friendly_id": note.friendly_id,
                "message": note.message,
                "created": note.created,
            }
        )

    return note_messages

그런 다음 쿼리 데이터를 가져와서 클라이언트에 메모를 표시합니다.

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  }).then(function(data){
    $('#notes-container').empty();
    // Iterate over user data to display user's notes from database.
    data.forEach(function(note){
      $('#notes-container').append($('<p>').text(note.message));
    });
  });
}

앱 배포

Identity Platform을 App Engine 애플리케이션과 성공적으로 통합했습니다. 실제 프로덕션 환경에서 애플리케이션의 실행을 확인하려면 다음 안내를 따르세요.

  1. main.js의 백엔드 호스트 URL을 https://backend-dot-[PROJECT_ID].appspot.com으로 변경합니다. 여기서 [PROJECT_ID]를 프로젝트 ID로 바꿉니다.
  2. Google Cloud SDK 명령줄 인터페이스를 사용하여 애플리케이션을 배포합니다.

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. https://[PROJECT_ID].appspot.com에서 애플리케이션의 실제 작동을 확인합니다.

삭제

이 튜토리얼에서 사용한 리소스 비용이 Google Cloud 계정에 청구되지 않도록 하려면 App Engine 프로젝트를 삭제하세요.

프로젝트 삭제

비용이 청구되지 않도록 하는 가장 쉬운 방법은 튜토리얼에서 만든 프로젝트를 삭제하는 것입니다.

프로젝트를 삭제하는 방법은 다음과 같습니다.

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

다음 단계

  • Google Cloud에 대한 참조 아키텍처, 다이어그램, 권장사항 살펴보기. Cloud 아키텍처 센터를 살펴보세요.