실습 설정 안내 및 요구사항
계정과 진행 상황을 보호하세요. 이 실습을 실행하려면 항상 시크릿 브라우저 창과 실습 사용자 인증 정보를 사용하세요.

Google Cloud에서 애플리케이션 개발하기: 애플리케이션에 사용자 인증 및 인텔리전스 추가하기

실습 1시간 30분 universal_currency_alt 크레딧 5개 show_chart 중급
info 이 실습에는 학습을 지원하는 AI 도구가 통합되어 있을 수 있습니다.
이 콘텐츠는 아직 휴대기기에 최적화되지 않음
최상의 경험을 위해 데스크톱 컴퓨터에서 이메일로 전송된 링크를 사용하여 방문하세요.

개요

Cloud 클라이언트 라이브러리는 애플리케이션에서 Google Cloud API를 호출하는 데 권장되는 방법으로, 애플리케이션에 사용 중인 프로그래밍 언어의 고유한 규칙과 스타일을 사용합니다. Cloud 클라이언트 라이브러리는 인증 및 재시도 로직을 포함하여 서버와의 하위 수준 통신을 처리합니다.

Firestore는 자동 확장, 고성능, 간편한 애플리케이션 개발을 위해 설계된 빠른 속도의 완전 관리형 서버리스 NoSQL 문서 데이터베이스입니다.

Google API는 인증 및 승인에 OAuth 2.0 프로토콜을 사용합니다.

Secret Manager를 사용하면 API 키, 비밀번호, 인증서, 기타 민감한 정보를 바이너리 blob 또는 텍스트 문자열로 저장할 수 있습니다.

Cloud Translation API를 사용하면 웹사이트와 애플리케이션에서 프로그래매틱 방식으로 텍스트를 동적으로 번역할 수 있습니다. Cloud Translation은 100개 이상의 언어로 텍스트를 번역할 수 있으며 소스 텍스트의 언어를 감지할 수 있습니다.

이 실습에서는 도서 목록을 관리하는 Python 애플리케이션을 업데이트합니다. OAuth를 사용하여 애플리케이션에 로그인하는 기능을 추가하고 사용자가 도서를 추가, 수정 또는 삭제할 때 로그인하도록 요구할 것입니다.

또한 Cloud Translation API를 사용하여 도서 설명을 다른 언어로 번역합니다. 사용자의 기본 언어를 저장하는 사용자 프로필을 추가합니다.

학습할 내용

이 실습에서는 다음 내용을 알아봅니다.

  • 간단한 Python Flask 웹 애플리케이션을 만듭니다.
  • Secret Manager를 사용하여 민감한 애플리케이션 데이터를 저장합니다.
  • OAuth 2.0을 사용하여 애플리케이션에 사용자 로그인을 추가합니다.
  • Cloud Translation API를 사용하여 텍스트의 언어를 감지하고 텍스트를 번역합니다.

설정 및 요건

실습 시작 버튼을 클릭하기 전에

참고: 다음 안내를 확인하세요.

실습에는 시간제한이 있으며 일시중지할 수 없습니다. 실습 시작을 클릭하면 타이머가 시작됩니다. 이 타이머는 Google Cloud 리소스를 사용할 수 있는 시간이 얼마나 남았는지를 표시합니다.

Google Skills 실무형 실습을 통해 시뮬레이션이나 데모 환경이 아닌 실제 클라우드 환경에서 직접 실습 활동을 진행할 수 있습니다. 실습 시간 동안 Google Cloud에 로그인하고 액세스하는 데 사용할 수 있는 새로운 임시 사용자 인증 정보가 제공됩니다.

필요한 사항

이 실습을 완료하려면 다음이 필요합니다.

  • 표준 인터넷 브라우저(Chrome 브라우저 권장)
  • 실습을 끝까지 진행할 수 있는 충분한 시간
참고: 이미 개인용 Google Cloud 계정이나 프로젝트가 있어도 이 실습에서는 사용하지 마세요. 참고: Pixelbook을 사용하는 경우 시크릿 창을 열어 이 실습을 실행하세요.

실습을 시작하고 콘솔에 로그인하는 방법

  1. 실습 시작 버튼을 클릭합니다. 실습 비용을 결제해야 하는 경우 결제 수단을 선택할 수 있는 팝업이 열립니다. 왼쪽에 있는 패널에서 이 실습에 사용해야 하는 임시 사용자 인증 정보를 확인할 수 있습니다.

    사용자 인증 정보 패널

  2. 사용자 이름을 복사한 다음 Google 콘솔 열기를 클릭합니다. 실습에서 리소스가 실행되며 계정 선택 페이지를 표시하는 다른 탭이 열립니다.

    참고: 두 개의 탭을 각각 별도의 창으로 나란히 여세요.
  3. 계정 선택 페이지에서 다른 계정 사용을 클릭합니다. 로그인 페이지가 열립니다.

    다른 계정 사용 옵션이 강조 표시된 계정 대화상자를 선택합니다.

  4. 연결 세부정보 패널에서 복사한 사용자 이름을 붙여넣습니다. 그런 다음 비밀번호를 복사하여 붙여넣습니다.

참고: 연결 세부정보 패널에 표시된 사용자 인증 정보를 사용해야 합니다. Google Skills 사용자 인증 정보를 사용하지 마세요. 개인용 Google Cloud 계정이 있어도 이 실습에서는 사용하지 마세요(요금 청구 방지).
  1. 이후에 표시되는 페이지를 클릭하여 넘깁니다.
  • 이용약관에 동의하세요.
  • 임시 계정이므로 복구 옵션이나 2단계 인증을 추가하지 마세요.
  • 무료 평가판을 신청하지 않습니다.

잠시 후 Cloud 콘솔이 이 탭에서 열립니다.

참고: 왼쪽 상단에 있는 탐색 메뉴를 클릭하면 Google Cloud 제품 및 서비스 목록이 있는 메뉴를 볼 수 있습니다. Cloud 콘솔 메뉴

Google Cloud Shell 활성화하기

Google Cloud Shell은 다양한 개발 도구가 탑재된 가상 머신으로, 5GB의 영구 홈 디렉터리를 제공하며 Google Cloud에서 실행됩니다.

Google Cloud Shell을 사용하면 명령줄을 통해 Google Cloud 리소스에 액세스할 수 있습니다.

  1. Cloud 콘솔의 오른쪽 상단 툴바에서 'Cloud Shell 열기' 버튼을 클릭합니다.

    강조 표시된 Cloud Shell 아이콘

  2. 계속을 클릭합니다.

환경을 프로비저닝하고 연결하는 데 몇 분 정도 소요됩니다. 연결되면 사용자가 미리 인증되어 프로젝트가 PROJECT_ID로 설정됩니다. 예:

Cloud Shell 터미널에 강조 표시된 프로젝트 ID

gcloud는 Google Cloud의 명령줄 도구입니다. Cloud Shell에 사전 설치되어 있으며 명령줄 자동 완성을 지원합니다.

  • 다음 명령어를 사용하여 사용 중인 계정 이름을 나열할 수 있습니다.
gcloud auth list

출력:

Credentialed accounts: - @.com (active)

출력 예시:

Credentialed accounts: - google1623327_student@qwiklabs.net
  • 다음 명령어를 사용하여 프로젝트 ID를 나열할 수 있습니다.
gcloud config list project

출력:

[core] project =

출력 예시:

[core] project = qwiklabs-gcp-44776a13dea667a6 참고: gcloud 전체 문서는 gcloud CLI 개요 가이드를 참조하세요.

작업 1. Python 애플리케이션 및 필요한 리소스 설정하기

이 작업에서는 Python 애플리케이션을 다운로드하고 앱의 현재 버전에서 사용되는 리소스를 만듭니다.

참고: 대부분의 언어에서 들여쓰기는 코드의 가독성을 높이는 데 사용됩니다. Python은 들여쓰기를 사용하여 코드 블록을 나타내므로 들여쓰기가 올바르게 되어 있어야 합니다. 들여쓰기에 사용되는 공백 수는 일관되어야 합니다. 들여쓰기에 스페이스바와 탭을 혼합하여 사용하면 문제가 발생할 수도 있습니다. 이 실습에서는 Python 들여쓰기에 공백 4개를 사용합니다.

Firestore 데이터베이스 만들기

  1. Firestore 데이터베이스를 만들려면 Cloud Shell에서 다음 명령어를 실행합니다.

    gcloud firestore databases create --location={{{ project_0.default_region | region }}}

    Firestore 데이터베이스는 도서 및 사용자 프로필 데이터를 저장하는 데 사용됩니다.

  2. Cloud Shell을 승인하라는 메시지가 표시되면 승인을 클릭합니다.

올바른 권한으로 Cloud Storage 버킷 만들기

  1. Cloud Storage 버킷을 만들려면 다음 명령어를 실행합니다.

    gcloud storage buckets create gs://{{{ project_0.project_id | project_id}}}-covers --location={{{ project_0.default_region | region }}} --no-public-access-prevention --uniform-bucket-level-access

    Cloud Storage 버킷은 도서 표지 이미지를 저장하는 데 사용됩니다. 버킷은 균일한 버킷 수준 액세스 권한을 보유하며 공개 액세스 방지를 사용하지 않습니다.

    참고: 명령어가 실패하고 계정에 유효한 사용자 인증 정보가 없다는 오류가 표시되면 명령어를 다시 시도해 보세요. Qwiklabs 학생 계정의 권한이 아직 전파되지 않았을 수 있습니다.
  2. 버킷의 모든 객체를 공개적으로 읽을 수 있게 하려면 다음 명령어를 실행합니다.

    gcloud storage buckets add-iam-policy-binding gs://{{{ project_0.project_id | project_id}}}-covers --member=allUsers --role=roles/storage.legacyObjectReader

목표를 확인하려면 내 진행 상황 확인하기를 클릭합니다. Python 애플리케이션 및 필요한 리소스 설정하기

Python 코드를 Cloud Shell에 복사하기

  1. Cloud Storage 버킷에서 home 디렉터리로 Python 코드를 복사하려면 다음 명령어를 실행합니다.

    gcloud storage cp gs://cloud-training/devapps-foundations/code/lab2/bookshelf.zip ~ && unzip ~/bookshelf.zip -d ~ && rm ~/bookshelf.zip
  2. bookshelf 디렉터리의 내용을 확인하려면 다음 명령어를 실행합니다.

    cd ~ ls -R bookshelf

    3개의 Python 파일과 1개의 요구사항 파일, 4개의 템플릿 파일이 포함된 목록이 표시됩니다.

    bookshelf: booksdb.py main.py requirements.txt storage.py templates bookshelf/templates: base.html form.html list.html view.html

필수 종속 항목 설치하기

  1. 요구사항 파일의 종속 항목을 나열하려면 다음 명령어를 실행합니다.

    cat ~/bookshelf/requirements.txt

    요구사항 파일은 다음 종속 항목을 지정합니다.

    • Flask: Python 웹 애플리케이션을 설계하는 데 사용되는 웹 프레임워크 모듈입니다.
    • Gunicorn: Linux에서 실행되는 Python HTTP 서버입니다.
    • Cloud Logging: 애플리케이션의 정보를 기록하는 데 사용됩니다.
    • Firestore: 애플리케이션을 간편하게 개발하기 위해 설계된 빠른 속도의 완전 관리형 서버리스 NoSQL 문서 데이터베이스입니다.
    • Cloud Storage: Google Cloud의 통합 객체 스토리지입니다.
  2. 요구사항 파일에 종속 항목을 설치하려면 다음 명령어를 실행합니다.

    pip3 install -r ~/bookshelf/requirements.txt --user

    pip는 Python용 패키지 설치 프로그램입니다. 이 pip3 명령어는 Python 버전 3에서 사용할 수 있도록 requirements.txt 파일에 지정된 패키지를 설치합니다.

애플리케이션 테스트하기

  1. 애플리케이션을 시작하려면 다음 명령어를 실행합니다.

    cd ~/bookshelf; ~/.local/bin/gunicorn -b :8080 main:app

    파일을 성공적으로 생성했다면 이제 애플리케이션이 포트 8080에서 호스팅될 것입니다.

  2. 웹브라우저에서 애플리케이션을 실행하려면 웹 미리보기를 클릭하고 포트 8080에서 미리보기를 선택합니다.

    포트 8080에서 웹 미리보기

    브라우저에 새 탭이 열리고 애플리케이션이 실행됩니다. 이 페이지에는 모든 기존 도서의 목록이 표시됩니다. 아직 도서가 없습니다.

    참고: Cloud Shell을 승인하라는 메시지가 표시되면 승인을 클릭합니다.
  3. 오즈의 마법사 도서 표지 이미지를 마우스 오른쪽 버튼으로 클릭하고 oz.png로 컴퓨터에 저장합니다.

    오즈의 마법사 도서 표지

  4. 애플리케이션 탭에서 +도서 추가를 클릭합니다.

  5. 양식에 다음 정보를 입력합니다.

    필드
    제목 오즈의 마법사
    저자 프랭크 L. 바움
    출판일 1900년
    설명 어린 소녀와 소녀의 강아지가 마법의 땅으로 휩쓸려가는데 그곳에서 이상한 사람들을 많이 만나고 집 아래에 서 있으면 안 된다는 교훈을 얻습니다.
  6. 표지 이미지에서 파일 선택을 클릭합니다.

  7. 다운로드한 파일(oz.png)을 선택하고 열기를 클릭합니다.

  8. 저장을 클릭합니다.

    뷰 페이지로 돌아가면 도서 세부정보가 표시됩니다.

  9. 페이지 상단에서 도서를 클릭합니다.

    목록 페이지로 돌아가면 오즈의 마법사가 도서 표지와 함께 목록에 표시됩니다. 도서 세부정보는 Firestore 데이터베이스에 저장되고 표지 이미지는 Cloud Storage에 저장됩니다.

    참고: 다른 도서를 추가할 수 있지만 Oz는 수정하지 마세요. 이 실습의 나머지 부분에서 사용됩니다.
  10. Cloud Shell에서 CTRL-C를 입력하여 애플리케이션을 종료합니다.

목표를 확인하려면 내 진행 상황 확인하기를 클릭합니다. 애플리케이션 테스트 및 도서 만들기

작업 2. 애플리케이션의 OAuth 승인 사용자 인증 정보 만들기

이 작업에서는 Google의 OAuth 2.0 서버에서 애플리케이션을 식별하는 승인 사용자 인증 정보를 만듭니다.

OAuth 동의 화면 만들기

승인에 OAuth 2.0을 사용하면 앱에서 Google 계정의 액세스 범위를 하나 이상 요청합니다. Google에서는 사용자에게 애플리케이션과의 데이터 공유에 대한 사용자 동의를 구하는 동의 화면을 표시합니다.

  1. Google Cloud 콘솔에서 탐색 메뉴(탐색 메뉴 아이콘)를 선택한 다음 API 및 서비스 > OAuth 동의 화면을 선택합니다.

    이 페이지에서는 애플리케이션을 사용할 사용자의 유형을 선택할 수 있습니다. 내부 사용자는 조직 내 사용자입니다. 외부 사용자는 Google 계정이 있는 모든 사용자입니다.

  2. 시작하기를 클릭합니다.

  3. 앱 이름Bookshelf를 입력합니다.

  4. 사용자 지원 이메일에 학생 이메일을 선택합니다.

  5. 다음을 클릭합니다.

  6. 대상에서 외부를 선택하고 다음을 클릭합니다.

    테스트 계정이 있는 사용자는 앱에 로그인할 수 있습니다.

  7. 실습 안내의 왼쪽 패널에서 사용자 이름을 복사합니다.

    사용자 이름 복사

  8. 이메일 주소에 복사한 사용자 이름을 붙여넣고 다음을 클릭합니다.

  9. 체크박스를 사용 설정하여 사용자 데이터 정책에 동의하고 계속을 클릭합니다.

  10. 만들기를 클릭합니다.

  11. 탐색 메뉴에서 브랜딩을 클릭합니다.

  12. + 도메인 추가를 클릭합니다.

  13. 승인된 도메인 섹션의 승인된 도메인 1cloudshell.dev를 입력합니다.

    애플리케이션이 Cloud Shell에서 실행 중일 때는 cloudshell.dev가 도메인 이름입니다.

  14. 저장을 클릭합니다.

  15. 탐색 메뉴에서 데이터 액세스를 클릭합니다.

    이제 애플리케이션 사용자에게 요청할 범위를 선택해야 합니다. 범위는 애플리케이션이 액세스하려는 사용자의 Google 계정에 있는 비공개 사용자 데이터의 유형을 나타냅니다.

    범위에는 세 가지 유형이 있습니다.

    • 민감한 범위는 동의 화면에서 사용자에게 표시되기 전에 Google의 인증을 받아야 합니다.
    • 제한된 범위는 Gmail, Drive와 같은 앱의 더욱 민감한 정보를 포함하며 더 광범위한 검토가 필요할 수 있습니다.
    • 민감하지 않은 범위는 민감도가 낮은 범위로, Google의 인증이 필요하지 않습니다.
  16. 범위 추가 또는 삭제를 클릭합니다.

    범위 목록이 표시됩니다.

  17. 목록의 맨 위에서 openid 옆의 체크박스를 선택합니다.

  18. 필터userinfo.profile을 입력하고 Enter 키를 누른 후 .../auth/userinfo.profile 범위 옆에 있는 체크박스를 선택합니다.

  19. 필터에서 userinfo.profile 필터를 지우고 contacts를 입력한 후 Enter 키를 누르고 .../auth/contacts 범위의 체크박스를 선택합니다.

  20. 업데이트를 클릭합니다.

    민감하지 않은 범위 2개(openid 및 userinfo.profile)와 민감한 범위 1개(contacts)가 표시됩니다.

    참고: 이 실습에서는 연락처 범위를 사용하지 않고 예시로만 사용합니다. 애플리케이션은 애플리케이션에 필요한 최소 범위만 사용해야 합니다.
  21. 저장을 클릭합니다.

  22. 탐색 메뉴에서 대상을 클릭합니다.

    사용자가 외부 사용자이고 게시 상태가 테스트로 설정된 경우 테스트 사용자가 필요합니다.

  23. + 사용자 추가를 클릭합니다.

  24. 실습 안내의 왼쪽 패널에서 사용자 이름을 다시 복사합니다.

  25. 사용자 추가 창에서, 복사한 사용자 이름을 상자에 붙여넣은 다음 저장을 클릭합니다.

OAuth 2.0 사용자 인증 정보 만들기

  1. 탐색 메뉴에서 클라이언트를 클릭한 후 + 클라이언트 만들기를 클릭합니다.

  2. 애플리케이션 유형에서 웹 애플리케이션을 선택합니다.

  3. 이름Bookshelf를 입력합니다.

  4. 승인된 리디렉션 URI에서 + URI 추가를 클릭합니다.

    여기에 지정된 URI는 Google이 사용자 동의를 확보한 후 브라우저를 애플리케이션으로 다시 리디렉션할 때 사용됩니다.

  5. 리디렉션 URI를 가져오려면 Cloud Shell에서 다음 명령어를 실행합니다.

    echo "https://8080-${WEB_HOST}/oauth2callback"
  6. echo 명령어로 생성된 URI를 복사하여 URI 1에 붙여넣습니다.

  7. 만들기를 클릭합니다.

  8. JSON 다운로드를 클릭하고 클라이언트 보안 비밀번호 JSON을 로컬 머신에 저장합니다.

    클라이언트 보안 비밀번호 파일은 Google에서 앱을 인증하는 데 사용됩니다.

  9. 닫기를 클릭합니다.

  10. Cloud Shell에서 오른쪽 상단 툴바의 더보기(더보기 아이콘)를 클릭한 후 업로드를 클릭합니다.

  11. 파일 선택을 클릭하고 클라이언트 보안 비밀번호 JSON 파일을 선택한 다음 열기를 클릭합니다.

  12. 업로드를 클릭합니다.

    이제 클라이언트 보안 비밀번호 JSON 파일을 home 디렉터리에서 사용할 수 있습니다. 이 파일의 내용은 OAuth 프로세스 중에 사용됩니다.

  13. Cloud Shell에서 다음 명령어를 실행합니다.

    cat ~/client_secret_*.json

    JSON 콘텐츠에는 client_secret이 포함되어 있으며 이는 비밀번호처럼 취급해야 합니다. 예를 들어 이 JSON 파일은 코드 저장소에 저장해서는 안 됩니다.

Secret Manager에 클라이언트 보안 비밀번호 JSON 저장하기

Secret Manager는 클라이언트 보안 비밀번호 JSON 파일을 저장하기에 권장되는 안전한 장소입니다.

  1. Secret Manager API를 사용 설정하려면 다음 명령어를 실행합니다.

    gcloud services enable secretmanager.googleapis.com
  2. 클라이언트 보안 비밀번호 파일의 이름을 바꾸려면 다음 명령어를 실행합니다.

    mv ~/client_secret*.json ~/client_secret.json
  3. 보안 비밀을 만들려면 다음 명령어를 실행합니다.

    gcloud secrets create bookshelf-client-secrets --data-file=$HOME/client_secret.json

    이제 애플리케이션에서 액세스할 수 있는 bookshelf-client-secrets라는 보안 비밀이 있습니다.

    애플리케이션에 필요한 또 다른 보안 비밀 값은 Flask 보안 비밀 키이며 쿠키의 정보에 서명하는 데 사용됩니다.

  4. Flask 보안 비밀 키의 보안 비밀을 만들려면 다음 명령어를 실행합니다.

    tr -dc A-Za-z0-9 </dev/urandom | head -c 20 | gcloud secrets create flask-secret-key --data-file=-

    이 명령어는 20자리의 영숫자 비밀번호를 무작위로 생성한 후 보안 비밀 flask-secret-key에 저장합니다.

목표를 확인하려면 내 진행 상황 확인하기를 클릭합니다. 애플리케이션의 OAuth 승인 사용자 인증 정보 만들기

작업 3. 애플리케이션에서 보안 비밀을 검색하는 데 Secret Manager 사용하기

이 작업에서는 Secret Manager를 사용하도록 애플리케이션을 수정합니다.

요구사항 파일을 수정하여 Secret Manager 추가

  1. 다음 명령어를 사용하여 nanorequirements.txt 파일을 엽니다.

    nano ~/bookshelf/requirements.txt
  2. 요구사항 파일에서 아래쪽 화살표를 사용하여 첫 번째 빈 줄로 이동한 후 다음 줄을 추가합니다.

    google-cloud-secret-manager==2.24.0

    이제 requirements.txt 파일이 다음과 같이 표시됩니다.

    Flask==3.1.1 gunicorn==23.0.0 google-cloud-logging==3.12.1 google-cloud-firestore==2.21.0 google-cloud-storage==2.17.0 google-cloud-secret-manager==2.24.0
  3. 파일을 저장하고 종료하려면 Ctrl+X 키를 누르고 Y 키를 누른 후 Enter 키를 누릅니다.

  4. 업데이트된 버전의 종속 항목을 설치하려면 다음 명령어를 실행합니다.

    pip3 install -r ~/bookshelf/requirements.txt --user

보안 비밀을 검색하는 함수 만들기

secrets.py 파일에는 Secret Manager에서 보안 비밀을 검색하는 코드가 포함되어 있습니다.

  1. secrets.py 파일을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/secrets.py <<EOF import os from google.cloud import secretmanager def get_secret(secret_id, version_id='latest'): # create the secret manager client client = secretmanager.SecretManagerServiceClient() # build the resource name of the secret version project_id = os.getenv('GOOGLE_CLOUD_PROJECT') name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}" # access the secret version response = client.access_secret_version(name=name) # return the decoded secret return response.payload.data.decode('UTF-8') EOF

    get_secret() 함수는 보안 비밀 ID와 선택적 버전 ID를 허용합니다. 요청된 보안 비밀이 함수에서 반환됩니다.

Secret Manager를 사용하도록 main.py 수정하기

기본 코드 파일은 Secret Manager를 호출하여 보안 비밀을 검색해야 합니다.

  1. 파일 편집기에서 ~/bookshelf/main.py 파일을 엽니다.

  2. storage의 import 문 뒤에 다음 줄을 추가합니다.

    import secrets

    이 줄은 방금 만든 secrets.py 파일을 가져옵니다.

  3. app.config.update 함수 호출에서 다음의 SECRET_KEY를 변경합니다.

    SECRET_KEY='secret', # don't store SECRET_KEY in code in a production app

    다음과 같이 변경합니다.

    SECRET_KEY=secrets.get_secret('flask-secret-key'),

    Flask 보안 비밀 키가 더 이상 애플리케이션 코드에 저장되지 않습니다.

  4. 파일을 저장합니다.

작업 4. OAuth 흐름을 위한 함수 만들기

이 작업에서는 OAuth 로그인 흐름을 관리하는 함수를 추가합니다.

사용자가 웹 애플리케이션에 로그인하면 앱은 OAuth 승인 시퀀스를 시작합니다. OAuth를 사용하면 사용자가 애플리케이션에서 요청한 액세스를 인증하고 동의할 수 있습니다. OAuth 승인 시퀀스는 다음과 같습니다.

OAuth 승인 시퀀스

승인 시퀀스는 애플리케이션이 브라우저를 Google URL로 리디렉션할 때 시작됩니다. Google에서는 사용자 인증, 세션 선택, 사용자 동의를 처리하며 그 결과가 승인 코드인 셈입니다. 애플리케이션은 이 코드를 액세스 토큰과 갱신 토큰으로 교환할 수 있습니다.

애플리케이션은 나중에 사용할 수 있도록 갱신 토큰을 저장하고 액세스 토큰을 사용하여 Google API에 액세스합니다. Bookshelf 애플리케이션은 Google API를 호출하여 사용자 정보를 검색합니다. 액세스 토큰이 만료되면 애플리케이션은 갱신 토큰을 사용하여 새 액세스 토큰을 가져옵니다.

종속 항목 추가하기

Google용 Python OAuth 클라이언트에는 3개의 Python 패키지가 추가로 필요합니다.

  1. ~/bookshelf/requirements.txt 파일에 다음 줄을 추가합니다.

    google-api-python-client==2.178.0 google-auth==2.40.3 google-auth-oauthlib==1.2.2

    이제 requirements.txt 파일이 다음과 같이 표시됩니다.

    Flask==3.1.1 gunicorn==23.0.0 google-cloud-logging==3.12.1 google-cloud-firestore==2.21.0 google-cloud-storage==2.17.0 google-cloud-secret-manager==2.24.0 google-api-python-client==2.178.0 google-auth==2.40.3 google-auth-oauthlib==1.2.2
  2. 파일을 저장합니다.

  3. 업데이트된 버전의 종속 항목을 설치하려면 다음 명령어를 실행합니다.

    pip3 install -r ~/bookshelf/requirements.txt --user

승인 흐름을 관리하는 OAuth 함수 추가하기

oauth.py 파일에는 Google에서 OAuth 토큰을 검색하는 코드가 포함되어 있습니다.

  1. oauth.py 파일을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/oauth.py <<EOF import google.oauth2.credentials import google_auth_oauthlib.flow from uuid import uuid4 from googleapiclient.discovery import build from werkzeug.exceptions import Unauthorized def _credentials_to_dict(credentials): """ Convert credentials mapping (object) into a dictionary. """ return { 'token': credentials.token, 'refresh_token': credentials.refresh_token, 'token_uri': credentials.token_uri, 'client_id': credentials.client_id, 'client_secret': credentials.client_secret, 'scopes': credentials.scopes, 'id_token': credentials.id_token, } def authorize(callback_uri, client_config, scopes): """ Builds the URL that will be used for redirection to Google to start the OAuth flow. """ # specify the flow configuration details flow = google_auth_oauthlib.flow.Flow.from_client_config( client_config=client_config, scopes=scopes, ) flow.redirect_uri = callback_uri # create a random state state = str(uuid4()) # get the authorization URL authorization_url, state = flow.authorization_url( # offline access allows access token refresh without reprompting the user # using online here to force log in access_type='online', state=state, prompt='consent', include_granted_scopes='false', ) return authorization_url, state def handle_callback(callback_uri, client_config, scopes, request_url, stored_state, received_state): """ Fetches credentials using the authorization code in the request URL, and retrieves user information for the logged-in user. """ # validate received state if received_state != stored_state: raise Unauthorized(f'Invalid state parameter: received={received_state} stored={stored_state}') # specify the flow configuration details flow = google_auth_oauthlib.flow.Flow.from_client_config( client_config=client_config, scopes=scopes ) flow.redirect_uri = callback_uri # get a token using the details in the request flow.fetch_token(authorization_response=request_url) credentials = flow.credentials oauth2_client = build('oauth2','v2',credentials=credentials, cache_discovery=False) user_info = oauth2_client.userinfo().get().execute() return _credentials_to_dict(credentials), user_info EOF

    authorize() 함수는 승인 시퀀스를 시작합니다. 이 함수는 전달된 client_config 파라미터를 사용하여 흐름을 구성하며 이 파라미터는 보안 비밀로 저장된 OAuth 구성 JSON 문자열에서 빌드됩니다. callback_uri는 Google이 승인 코드를 사용하여 호출할 위치를 지정합니다. 이 URI는 Bookshelf 애플리케이션에 대해 승인된 리디렉션 URI로 구성된 URI와 일치해야 합니다. flow.authorization_url() 호출은 Google로 리디렉션하기 위한 전체 URL을 빌드합니다. 상태가 생성되어 전달되며, 이 상태는 세션에 저장되어 이 호출을 최종 콜백과 일치시킵니다. 승인 URL과 상태가 호출자에 반환됩니다.

    handle_callback() 함수는 Google에서 콜백을 수신할 때 사용됩니다. 콜백 URL에 지정된 상태는 승인 URL에 전송된 저장된 상태와 일치해야 합니다. 그러면 flow.fetch_token() 호출을 사용하여 갱신 토큰과 액세스 토큰을 포함한 사용자 인증 정보를 가져올 수 있습니다. 반환된 사용자 인증 정보는 Google을 호출하고 로그인한 사용자의 사용자 정보를 수신하는 데 사용합니다. 그런 다음 사용자 인증 정보와 사용자 정보가 호출자에 반환됩니다.

    참고: oauth2_client.userinfo() 줄에는 userinfo 멤버가 없다는 오류 메시지가 IDE에 표시될 수 있습니다. 리소스 객체는 build()에 의해 반환되며, 유효한 멤버는 컴파일 시간에 알려지지 않습니다. 이 오류는 무시해도 됩니다.

작업 5. 로그인, 콜백, 로그아웃 엔드포인트 추가하기

이 작업에서는 방금 만든 OAuth 함수를 사용하여 로그인, 로그아웃, 콜백 엔드포인트를 구현합니다.

HTML 템플릿 수정하기

  1. 오류 템플릿을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/templates/error.html <<EOF {% extends "base.html" %} {% block content %} <h3>Error: {{error_message}}</h3> {% endblock %} EOF

    로그인할 때 사용자에게 오류가 발생할 수 있습니다. 이 페이지에서는 해당 오류를 표시합니다.

  2. 파일 편집기에서 ~/bookshelf/templates/base.html 파일을 엽니다.

    로그인 및 로그아웃 링크가 애플리케이션에 추가됩니다.

  3. navbar 섹션에서 class="nav navbar-nav"가 있는 순서가 지정되지 않은 목록(ul)닫는 태그(/ul) 다음 줄에 다음 섹션을 추가합니다.

    <ul class="nav navbar-nav navbar-right"> {% if session['credentials'] %} <div class="navbar-brand">{{session['user'].email}}</div> <div class="navbar-brand"><a href="/logout">Logout</a></div> {% else %} <div class="navbar-brand"><a href="/login">Login</a></div> {% endif %} </ul>

    이제 파일이 다음과 같이 표시됩니다.

    <!DOCTYPE html> <html lang="en"> <head> <title>Bookshelf</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> </head> <body> <div class="navbar navbar-default"> <div class="container"> <div class="navbar-header"> <div class="navbar-brand">Bookshelf</div> </div> <ul class="navbar-nav"> <li><a href="/">Books</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {% if session['credentials'] %} <div class="navbar-brand">{{session['user'].email}}</div> <div class="navbar-brand"><a href="/logout">Logout</a></div> {% else %} <div class="navbar-brand"><a href="/login">Login</a></div> {% endif %} </ul> </div> </div> <div class="container"> {% block content %}{% endblock %} </div> </body> </html>

    이제 기본 템플릿은 세션을 사용하여 사용자가 로그인했는지 확인합니다. 사용자가 로그인한 경우 사용자의 이메일 주소와 로그아웃 링크가 표시됩니다. 사용자가 로그인하지 않은 경우 로그인 링크가 표시됩니다.

  4. 파일을 저장합니다.

가져오기 수정하기

  1. 파일 편집기에서 ~/bookshelf/main.py 파일을 엽니다.

  2. flask 가져오기에 session을 추가합니다.

    이제 flask 가져오기가 다음과 같이 표시됩니다.

    from flask import current_app, Flask, redirect, render_template from flask import request, url_for, session

    세션은 로그인한 사용자와 연결된 정보에 대한 액세스 권한을 제공하며, 세션 데이터는 쿠키에 저장됩니다.

  3. cloud_logging import 줄 뒤에 다음 줄을 추가합니다.

    import json import os from urllib.parse import urlparse

    main.py에서는 json 라이브러리를 사용하여 클라이언트 보안 비밀번호 문자열을 매핑(객체)으로 변환하고, os 라이브러리를 사용하여 환경 변수를 사용합니다. urlparse() 함수는 URL의 스키마와 호스트 이름을 바꾸는 데 사용됩니다.

  4. secrets의 import 문 뒤에 다음 줄을 추가합니다.

    import oauth

    이를 통해 개발자가 생성한 OAuth 함수를 가져옵니다.

구성 항목 추가하기

  1. app.config.update() 함수 호출에서 ALLOWED_EXTENSIONS 줄 뒤에 다음 줄을 추가합니다.

    CLIENT_SECRETS=json.loads(secrets.get_secret('bookshelf-client-secrets')), SCOPES=[ 'openid', 'https://www.googleapis.com/auth/contacts.readonly', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile', ], EXTERNAL_HOST_URL=os.getenv('EXTERNAL_HOST_URL'),

    다음 구성 항목이 추가됩니다.

    • CLIENT_SECRETS에는 Secret Manager에 저장된 OAuth 구성의 매핑(객체)이 포함되어 있습니다.
    • SCOPES에는 요청할 범위 목록이 포함되어 있습니다.
    • EXTERNAL_HOST_URL은 콜백 URL을 결정하는 데 사용됩니다. Cloud Shell과 함께 웹 미리보기를 사용하면 localhost(127.0.0.1) 포트 80으로 실행되도록 설정된 애플리케이션이 https://8080-...-cloudshell.dev에서 인터넷에 노출됩니다. 이 URL은 콜백 엔드포인트의 localhost URL을 공개적으로 액세스할 수 있는 URL로 변환하는 데 사용됩니다. 값은 환경 변수로 전달됩니다.

엔드포인트 및 지원 함수 추가하기

  1. log_request() 함수(def log_request():로 시작) 뒤에 다음 함수를 추가합니다.

    def logout_session(): """ Clears known session items. """ session.pop('credentials', None) session.pop('user', None) session.pop('state', None) session.pop('error_message', None) session.pop('login_return', None) return def external_url(url): """ Cloud Shell routes https://8080-***/ to localhost over http This function replaces the localhost host with the configured scheme + hostname """ external_host_url = current_app.config['EXTERNAL_HOST_URL'] if external_host_url is None: # force https if url.startswith('http://'): url = f"https://{url[7:]}" return url # replace the scheme and hostname with the external host URL parsed_url = urlparse(url) replace_string = f"{parsed_url.scheme}://{parsed_url.netloc}" new_url = f"{external_host_url}{url[len(replace_string):]}" return new_url @app.route('/error') def error(): """ Display an error. """ log_request(request) if "error_message" not in session: return redirect(url_for('.list')) # render error return render_template('error.html', error_message=session.pop('error_message', None)) @app.route("/login") def login(): """ Login if not already logged in. """ log_request(request) if not "credentials" in session: # need to log in current_app.logger.info('logging in') # get authorization URL authorization_url, state = oauth.authorize( callback_uri=external_url(url_for('oauth2callback', _external=True)), client_config=current_app.config['CLIENT_SECRETS'], scopes=current_app.config['SCOPES']) current_app.logger.info(f"authorization_url={authorization_url}") # save state for verification on callback session['state'] = state return redirect(authorization_url) # already logged in return redirect(session.pop('login_return', url_for('.list'))) @app.route("/oauth2callback") def oauth2callback(): """ Callback destination during OAuth process. """ log_request(request) # check for error, probably access denied by user error = request.args.get('error', None) if error: session['error_message'] = f"{error}" return redirect(url_for('.error')) # handle the OAuth2 callback credentials, user_info = oauth.handle_callback( callback_uri=external_url(url_for('oauth2callback', _external=True)), client_config=current_app.config['CLIENT_SECRETS'], scopes=current_app.config['SCOPES'], request_url=external_url(request.url), stored_state=session.pop('state', None), received_state=request.args.get('state', '')) session['credentials'] = credentials session['user'] = user_info current_app.logger.info(f"user_info={user_info}") return redirect(session.pop('login_return', url_for('.list'))) @app.route("/logout") def logout(): """ Log out and return to root page. """ log_request(request) logout_session() return redirect(url_for('.list'))

    logout_session() 함수는 알려진 세션 항목을 지웁니다.

    external_url() 함수는 URL의 스키마와 호스트 이름을 외부 액세스를 위한 다른 호스트 이름으로 바꿉니다. 대체 호스트 이름이 지정되지 않은 경우 함수는 반환된 URL이 https를 사용하도록 합니다.

    /error 엔드포인트는 오류를 표시하는 데 사용됩니다.

    /login 엔드포인트는 세션을 확인하여 사용자가 로그인했는지 확인합니다. 사용자 인증 정보가 세션에 저장되어 있으면 사용자가 로그인한 것입니다. 사용자가 로그인되어 있지 않으면 oauth.authorize()가 호출되어 Google로 리디렉션하기 위한 승인 URL과 상태를 가져옵니다. 상태는 세션에 저장되고 브라우저는 승인 URL로 리디렉션됩니다.

    /oauth2callback 엔드포인트는 승인 프로세스 중에 Google에서 호출합니다. 오류가 발생한 경우 프로세스가 실패하고 사용자는 오류 페이지로 리디렉션됩니다. 오류가 아닌 경우 handle_callback()이 호출되어 토큰과 사용자 정보를 검색합니다. 프로세스가 끝나면 사용자는 로그인이 자동으로 시작된 이전 페이지로 리디렉션되거나, 반환 위치가 없는 경우 루트 페이지(도서 목록)로 리디렉션됩니다.

    /logout 엔드포인트는 세션에서 사용자 인증 정보와 사용자 데이터를 삭제하여 사용자를 로그아웃시키고 루트 페이지로 돌아갑니다.

도서를 추가, 수정 또는 삭제할 때 강제 로그인하기

사용자는 로그인하지 않고도 서가에 있는 도서를 둘러볼 수 있습니다. 하지만 사용자가 도서를 수정하기 전에 로그인하도록 강제하는 것이 좋습니다.

사용자가 도서를 추가, 수정 또는 삭제하려고 하는데 로그인하지 않은 경우 로그인을 강제해야 합니다.

  1. add() 함수에서 log_request() 호출 바로 뒤에 다음 줄을 추가합니다.

    # must be logged in if "credentials" not in session: session['login_return'] = url_for('.add') return redirect(url_for('.login'))

    add()에서 사용자가 로그인되어 있지 않으면 로그인 후 추가 페이지로 돌아갑니다.

  2. edit() 함수에서 log_request() 호출 바로 뒤에 다음 줄을 추가합니다.

    # must be logged in if "credentials" not in session: session['login_return'] = url_for('.edit', book_id=book_id) return redirect(url_for('.login'))

    edit()에서 사용자가 로그인되어 있지 않으면 로그인 후 이 도서의 수정 페이지로 돌아갑니다.

  3. delete() 함수에서 log_request() 호출 바로 뒤에 다음 줄을 추가합니다.

    # must be logged in if "credentials" not in session: session['login_return'] = url_for('.view', book_id=book_id) return redirect(url_for('.login'))

    delete()에서 사용자가 로그인되어 있지 않으면 로그인 후 이 도서의 뷰 페이지로 돌아갑니다.

  4. 파일을 저장합니다.

애플리케이션 테스트하기

  1. HTTP 서버를 시작하려면 Cloud Shell에서 다음 명령어를 실행합니다.

    cd ~/bookshelf; EXTERNAL_HOST_URL="https://8080-$WEB_HOST" ~/.local/bin/gunicorn -b :8080 main:app

    애플리케이션에 전달되는 환경 변수가 있습니다.

    • EXTERNAL_HOST_URL은 콜백 URL에 사용해야 하는 스키마와 호스트 이름을 지정합니다. 이 환경 변수를 지정하지 않으면 승인 URL에서 Google에 전달된 redirect_uri가 애플리케이션이 수신 URL에서 보는 호스트 이름인 127.0.0.1:8080(localhost)을 사용합니다. 웹 미리보기는 cloudshell.dev URL의 요청을 localhost(http://127.0.0.1:8080)로 전달합니다.
    참고: Cloud Shell을 승인하라는 메시지가 표시되면 승인을 클릭합니다.
  2. 웹브라우저에서 애플리케이션을 열려면 웹 미리보기를 클릭하고 포트 8080에서 미리보기를 선택합니다.

    포트 8080에서 웹 미리보기

    브라우저에 새 탭이 열리고 애플리케이션이 실행됩니다. 오즈의 마법사 책이 표시됩니다. 사용자는 로그인되어 있지 않습니다.

  3. + 도서 추가를 클릭합니다.

    도서를 추가하려면 로그인해야 하므로 Google로 로그인할 계정을 선택하라는 메시지가 표시됩니다.

    계정 선택

  4. 학생 이메일을 클릭한 후 계속을 클릭합니다.

    이제 Google에서는 요청되는 제한된 범위나 민감하지 않은 범위에 대해 동의를 얻습니다. 이 경우 모든 연락처를 다운로드하는 것은 민감한 범위입니다.

    동의 얻기

  5. 허용을 클릭합니다.

    도서 추가 페이지에서 애플리케이션으로 돌아갑니다. 오른쪽 상단에 이메일이 표시되어 있는 것으로 보아 로그인된 상태입니다. Cloud Shell에서 로그를 살펴보면 Google의 콜백 호출이 표시됩니다.

    INFO:main:REQ: GET http://127.0.0.1:8080/oauth2callback?state=88789b07-2474-423f-b572-f5d4a3240ace&code=4g0AfJohXm0vtB2eYHnRaAeM8m-VCmnssg5YgrjoJstTLmHaVq8nlbJo5uzIS67NbWTXTOqDw&scope=email%20profile%20openid%20https://www.googleapis.com/auth/contacts.readonly%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile&authuser=0&hd=qwiklabs.net&prompt=consent

    사용자가 액세스에 동의했으므로 code라는 파라미터에는 이후에 사용자 인증 정보로 교환된 승인 코드가 있습니다. 허용되는 범위도 반환됩니다.

  6. 도서를 클릭한 후 + 도서 추가를 클릭합니다.

    이미 로그인되어 있으므로 도서 추가 페이지로 바로 이동할 수 있습니다.

  7. 로그아웃을 클릭한 후 로그인을 클릭합니다.

  8. 학생 이메일을 클릭한 후 계속을 클릭하고 취소를 클릭합니다.

    오류 페이지로 돌아갑니다. Cloud Shell에서 로그를 살펴봅니다. Google에서 만든 콜백이 표시되며 다음과 같습니다.

    INFO:main:REQ: GET http://127.0.0.1:8080/oauth2callback?error=access_denied&state=72342071-c8dc-43be-8184-9f6bd6069cd5

    이 경우 동의가 제공되지 않았으므로 승인 코드가 반환되지 않았고 애플리케이션이 사용자의 사용자 인증 정보를 가져올 수 없습니다.

  9. Cloud Shell에서 CTRL-C를 입력하여 애플리케이션을 종료합니다.

목표를 확인하려면 내 진행 상황 확인하기를 클릭합니다. 로그인, 콜백, 로그아웃 엔드포인트 추가하기

작업 6. 번역 함수 만들기

이 작업에서는 Cloud Translation API를 사용하여 텍스트 언어를 감지하고 텍스트를 번역하는 함수를 만듭니다.

Cloud Translation API 호출에서는 애플리케이션의 사용자 인증 정보를 사용합니다.

Cloud Translation 종속 항목 추가

애플리케이션에는 다른 Python 패키지가 필요합니다.

  1. ~/bookshelf/requirements.txt 파일에 다음 줄을 추가합니다.

    google-cloud-translate==3.21.1

    이제 requirements.txt 파일이 다음과 같이 표시됩니다.

    Flask==3.1.1 gunicorn==23.0.0 google-cloud-logging==3.12.1 google-cloud-firestore==2.21.0 google-cloud-storage==2.17.0 google-cloud-secret-manager==2.24.0 google-api-python-client==2.178.0 google-auth==2.40.3 google-auth-oauthlib==1.2.2 google-cloud-translate==3.21.1
  2. 파일을 저장합니다.

  3. 업데이트된 종속 항목을 설치하려면 다음 명령어를 실행합니다.

    pip3 install -r ~/bookshelf/requirements.txt --user

번역 함수 추가하기

translate.py 파일에는 번역을 실행하는 코드가 포함되어 있습니다.

  1. translate.py 파일을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/translate.py <<EOF import os from google.cloud import translate PROJECT_ID = os.getenv('GOOGLE_CLOUD_PROJECT') PARENT = f"projects/{PROJECT_ID}" supported_languages = None def get_languages(): """ Gets the list of supported languages. """ # use the global variable global supported_languages # retrieve supported languages if not previously retrieved if not supported_languages: client = translate.TranslationServiceClient() response = client.get_supported_languages( parent=PARENT, display_language_code='en', ) supported_languages = response.languages return supported_languages def detect_language(text): """ Detect the language of the supplied text. Returns the most likely language. """ client = translate.TranslationServiceClient() response = client.detect_language( parent=PARENT, content=text, ) return response.languages[0] def translate_text(text, target_language_code): """ Translate the text to the target language. """ client = translate.TranslationServiceClient() response = client.translate_text( parent=PARENT, contents=[text], target_language_code=target_language_code, ) return response.translations[0] EOF

    get_languages() 함수는 Cloud Translation API에서 지원하는 언어 목록을 검색합니다. 목록의 각 언어에는 ID(language_code)와 표시 텍스트(display_name)가 포함되어 있습니다.

    detect_language() 함수는 텍스트 문자열의 언어를 감지합니다.

    translate_language() 함수는 텍스트를 지정된 언어로 번역합니다.

작업 7. 언어 선택을 위한 사용자 프로필 만들기 및 설명 번역하기

이 작업에서는 로그인한 사용자의 사용자 프로필을 만듭니다. 사용자의 기본 언어를 선택할 수 있습니다.

프로필은 profiles라는 Firestore 컬렉션에 저장됩니다. 사용자가 프로필을 업데이트할 때까지는 기본 언어가 영어인 기본 프로필이 사용됩니다.

프로필을 읽고 업데이트하는 함수 추가하기

profiledb.py 파일에는 사용자 프로필을 읽고 업데이트하는 코드가 포함되어 있습니다. 사용자의 이메일 주소가 프로필 키로 사용됩니다. 이 구현에서 프로필에 있는 유일한 항목은 기본 언어입니다.

  1. profiledb.py 파일을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/profiledb.py <<EOF from google.cloud import firestore default_profile = { "preferredLanguage": "en" } def __document_to_dict(doc): if not doc.exists: return None doc_dict = doc.to_dict() doc_dict['id'] = doc.id return doc_dict def read(email): """ Return a profile by email. """ db = firestore.Client() # retrieve a profile from the database by ID profile_ref = db.collection("profiles").document(email) profile_dict = __document_to_dict(profile_ref.get()) # return empty dictionary if no profile if profile_dict is None: profile_dict = default_profile.copy() return profile_dict def read_entry(email, key, default_value=''): """ Return a profile entry by email and key. """ profile_dict = read(email) return profile_dict.get(key, default_value) def update(data, email): """ Update a profile, and return the updated profile's details. """ db = firestore.Client() # update profile in database profile_ref = db.collection("profiles").document(email) profile_ref.set(data) return __document_to_dict(profile_ref.get()) EOF

    read() 함수는 지정된 사용자의 프로필을 검색합니다. 프로필을 찾을 수 없는 경우 기본 프로필의 사본이 반환됩니다.

    read_entry() 함수는 사용자의 프로필에서 단일 값을 반환합니다. 사용자 프로필에서 키를 찾을 수 없는 경우 전달된 기본값이 대신 반환됩니다.

    update() 함수는 지정된 데이터로 사용자 프로필을 생성하거나 덮어쓰기합니다.

사용자 프로필을 보고 수정할 수 있도록 프로필 엔드포인트 추가

  1. profile.html에서 사용자 프로필의 새 템플릿을 만들려면 다음 명령어를 실행합니다.

    cat > ~/bookshelf/templates/profile.html <<EOF {# [START form] #} {% extends "base.html" %} {% block content %} <h3>Profile for {{session['user']['email']}}</h3> <form method="POST" enctype="multipart/form-data"> <div class="form-group"> <label for="preferredLanguage">Preferred Language</label> <select id="preferredLanguage" name="preferredLanguage"> {% for l in languages %} {% if l.language_code == profile['preferredLanguage'] %} <option value="{{l.language_code}}" selected>{{l.display_name}}</option> {% else %} <option value="{{l.language_code}}">{{l.display_name}}</option> {% endif %} {% endfor %} </select> </div> <button type="submit" class="btn btn-success">Save</button> </form> {% endblock %} {# [END form] #} EOF

    이 템플릿은 선택 컨트롤과 제출 버튼이 있는 단일 양식을 만듭니다. 선택 컨트롤에는 languages 목록 변수를 사용하여 전달된 모든 언어가 로드됩니다. 각 항목의 값은 language_code이고 display_name은 선택 컨트롤에 표시됩니다. 처음에 표시되는 언어는 프로필에 지정된 preferredLanguage입니다.

  2. 파일 편집기에서 ~/bookshelf/main.py 파일을 엽니다.

  3. oauth의 import 문 뒤에 다음 줄을 추가합니다.

    import translate import profiledb

    이를 통해 방금 만든 translate.pyprofiledb.py 파일을 가져옵니다.

  4. /profile 엔드포인트를 추가하려면 /books/<book_id>/delete 엔드포인트 뒤에 다음 함수를 추가합니다.

    @app.route('/profile', methods=['GET', 'POST']) def profile(): """ If GET, show the form to collect updated details for the user profile. If POST, update the profile based on the specified form. """ log_request(request) # must be logged in if "credentials" not in session: session['login_return'] = url_for('.profile') return redirect(url_for('.login')) # read existing profile email = session['user']['email'] profile = profiledb.read(email) # Save details if form was posted if request.method == 'POST': # get book details from form data = request.form.to_dict(flat=True) # update profile profiledb.update(data, email) session['preferred_language'] = data['preferredLanguage'] # return to root return redirect(url_for('.list')) # render form to update book return render_template('profile.html', action='Edit', profile=profile, languages=translate.get_languages())

    프로필은 로그인한 사용자만 액세스할 수 있으므로 사용자가 아직 로그인하지 않은 경우 로그인 페이지로 리디렉션됩니다.

    로그인한 사용자의 이메일을 세션의 사용자 정보에서 가져온 후 현재 프로필을 읽습니다.

    프로필은 profile.html 템플릿을 사용하여 렌더링됩니다.

    제출 버튼을 클릭하면 프로필이 데이터베이스에서 업데이트되고, 사용자의 기본 언어가 세션에 저장되며, 브라우저가 루트 페이지로 리디렉션됩니다.

  5. 파일을 저장합니다.

사용자의 이메일 주소 클릭 시 프로필로 이동하기

  1. 파일 편집기에서 ~/bookshelf/templates/base.html 파일을 엽니다.

  2. 이 파일에서 다음 줄을 변경합니다.

    <div class="navbar-brand">{{session['user'].email}}</div>

    다음과 같이 변경합니다.

    <div class="navbar-brand"><a href="/profile">{{session['user'].email}}</a></div>

    이렇게 하면 표시된 이메일 주소가 /profile 엔드포인트로 리디렉션되는 클릭 가능한 링크로 변경됩니다.

  3. 파일을 저장합니다.

뷰 페이지의 설명 번역하기

  1. 파일 편집기에서 ~/bookshelf/templates/view.html 파일을 엽니다.

  2. 이 파일에서 다음 줄을 변경합니다.

    <p class="book-description">{{book.description}}</p>

    다음과 같이 변경합니다.

    {% if translation_language is not none %} <p class="book-description"><strong>Description ({{description_language}}): </strong>{{book.description}}</p> <p class="book-description"><strong>Translation ({{translation_language}}): </strong>{{translated_text}}</p> {% else %} <p class="book-description"><strong>Description: </strong>{{book.description}}</p> {% endif %}

    translation_language가 지정되지 않은 경우 설명은 변경되지 않습니다. 그러나 번역 언어가 있는 경우 원본 설명의 언어가 표시되고 다음 줄에 번역된 버전과 텍스트가 표시됩니다. 뷰 엔드포인트는 이 추가 정보를 전달해야 합니다.

  3. 파일을 저장합니다.

  4. 파일 편집기에서 ~/bookshelf/main.py 파일을 엽니다.

  5. log_request() 함수(def log_request():로 시작) 뒤에 다음 코드를 추가합니다.

    # build a mapping of language codes to display names display_languages = {} for l in translate.get_languages(): display_languages[l.language_code] = l.display_name

    detect_language() 함수는 감지된 언어 코드를 반환하지만 표시 이름은 반환하지 않습니다. 이 코드는 언어 코드에서 표시 이름으로의 매핑을 만듭니다. 이는 감지된 언어를 뷰 템플릿에 표시하는 데 사용됩니다.

  6. 전체 뷰 엔드포인트 코드를 다음 코드로 바꿉니다.

    @app.route('/books/<book_id>') def view(book_id): """ View the details of a specified book. """ log_request(request) # retrieve a specific book book = booksdb.read(book_id) current_app.logger.info(f"book={book}") # defaults if logged out description_language = None translation_language = None translated_text = '' if book['description'] and "credentials" in session: preferred_language = session.get('preferred_language', 'en') # translate description translation = translate.translate_text( text=book['description'], target_language_code=preferred_language, ) description_language = display_languages[translation.detected_language_code] translation_language = display_languages[preferred_language] translated_text = translation.translated_text # render book details return render_template('view.html', book=book, translated_text=translated_text, description_language=description_language, translation_language=translation_language, )

    이제 코드가 도서 설명을 사용자의 기본 언어로 번역하고 번역과 언어를 템플릿에 전달합니다.

  7. 파일을 저장합니다.

작업 8. 애플리케이션 테스트하기

  1. HTTP 서버를 시작하려면 다음 명령어를 실행합니다.

    cd ~/bookshelf; EXTERNAL_HOST_URL="https://8080-$WEB_HOST" ~/.local/bin/gunicorn -b :8080 main:app
  2. 웹브라우저에서 애플리케이션을 열려면 웹 미리보기를 클릭하고 포트 8080에서 미리보기를 선택합니다.

    포트 8080에서 웹 미리보기

    브라우저에 새 탭이 열리고 애플리케이션이 실행됩니다. 오즈의 마법사 책이 표시됩니다.

    참고: Cloud Shell을 승인하라는 메시지가 표시되면 승인을 클릭합니다.
  3. 사용자가 로그인하지 않은 경우 로그인을 클릭한 후 동의를 제공하여 사용자를 로그인합니다.

    이제 이메일 주소가 링크로 표시됩니다.

  4. 이메일 주소를 클릭합니다.

    프로필이 표시됩니다. 언어 선택 컨트롤에 English가 표시되어야 합니다.

  5. 기본 언어스와힐리어로 변경한 후 저장을 클릭합니다.

  6. 도서 오즈의 마법사를 클릭합니다.

    이제 뷰 페이지에 영어 원본 설명과 스와힐리어 번역이 모두 포함됩니다.

목표를 확인하려면 내 진행 상황 확인하기를 클릭합니다. 애플리케이션 테스트

수고하셨습니다

OAuth를 이용한 사용자 로그인을 위해 애플리케이션을 성공적으로 수정했습니다. 그런 다음 기본 언어로 사용자 프로필을 추가하고 Cloud Translation API를 사용하여 도서 설명에 대한 번역을 제공했습니다.

다음 단계/더 학습하기

실습 종료하기

실습을 완료하면 실습 종료를 클릭합니다. Google Cloud Skills Boost에서 사용된 리소스를 자동으로 삭제하고 계정을 지웁니다.

실습 경험을 평가할 수 있습니다. 해당하는 별표 수를 선택하고 의견을 입력한 후 제출을 클릭합니다.

별점의 의미는 다음과 같습니다.

  • 별표 1개 = 매우 불만족
  • 별표 2개 = 불만족
  • 별표 3개 = 중간
  • 별표 4개 = 만족
  • 별표 5개 = 매우 만족

의견을 제공하고 싶지 않다면 대화상자를 닫으면 됩니다.

의견이나 제안 또는 수정할 사항이 있다면 지원 탭을 사용하세요.

Copyright 2026 Google LLC All rights reserved. Google 및 Google 로고는 Google LLC의 상표입니다. 기타 모든 회사명 및 제품명은 해당 업체의 상표일 수 있습니다.

시작하기 전에

  1. 실습에서는 정해진 기간 동안 Google Cloud 프로젝트와 리소스를 만듭니다.
  2. 실습에는 시간 제한이 있으며 일시중지 기능이 없습니다. 실습을 종료하면 처음부터 다시 시작해야 합니다.
  3. 화면 왼쪽 상단에서 실습 시작을 클릭하여 시작합니다.

시크릿 브라우징 사용

  1. 실습에 입력한 사용자 이름비밀번호를 복사합니다.
  2. 비공개 모드에서 콘솔 열기를 클릭합니다.

콘솔에 로그인

    실습 사용자 인증 정보를 사용하여
  1. 로그인합니다. 다른 사용자 인증 정보를 사용하면 오류가 발생하거나 요금이 부과될 수 있습니다.
  2. 약관에 동의하고 리소스 복구 페이지를 건너뜁니다.
  3. 실습을 완료했거나 다시 시작하려고 하는 경우가 아니면 실습 종료를 클릭하지 마세요. 이 버튼을 클릭하면 작업 내용이 지워지고 프로젝트가 삭제됩니다.

현재 이 콘텐츠를 이용할 수 없습니다

이용할 수 있게 되면 이메일로 알려드리겠습니다.

감사합니다

이용할 수 있게 되면 이메일로 알려드리겠습니다.

한 번에 실습 1개만 가능

모든 기존 실습을 종료하고 이 실습을 시작할지 확인하세요.

시크릿 브라우징을 사용하여 실습 실행하기

이 실습을 실행하는 가장 좋은 방법은 시크릿 모드 또는 시크릿 브라우저 창을 사용하는 것입니다. 개인 계정과 학생 계정 간의 충돌로 개인 계정에 추가 요금이 발생하는 일을 방지해 줍니다.