반복 로그인 처리

이 내용은 클래스룸 부가기능 둘러보기 시리즈의 세 번째 둘러보기입니다.

이 둘러보기에서는 이전에 부여된 사용자 인증 정보를 자동으로 검색하여 부가기능에 대한 반복 방문을 처리합니다. 그런 다음 API 요청을 즉시 실행할 수 있는 페이지로 사용자를 라우팅합니다. 이는 클래스룸 부가기능의 필수 동작입니다.

이 둘러보기 과정에서 다음을 완료합니다.

  • 사용자 인증 정보를 위한 영구 저장소를 구현합니다.
  • login_hint 부가기능 쿼리 매개변수를 가져오고 평가합니다. 이 ID는 로그인한 사용자의 고유한 Google ID 번호입니다.

완료되면 웹 앱에서 사용자를 완전히 승인하고 Google API를 호출할 수 있습니다.

iframe 쿼리 매개변수 이해

클래스룸을 열면 부가기능의 첨부파일 설정 URI가 로드됩니다. 클래스룸은 URI에 여러 GET 쿼리 매개변수를 추가합니다. 이러한 매개변수는 유용한 상황 정보를 포함합니다. 예를 들어 첨부파일 검색 URI가 https://example.com/addon이면 클래스룸은 소스 URL이 https://example.com/addon?courseId=XXX&itemId=YYY&itemType=courseWork&addOnToken=ZZZ로 설정된 iframe을 만듭니다. 여기서 XXX, YYY, ZZZ는 문자열 ID입니다. 이 시나리오의 자세한 설명은 iframe 가이드를 참고하세요.

검색 URL에 사용할 수 있는 쿼리 매개변수는 5가지입니다.

  • courseId: 현재 클래스룸 과정의 ID입니다.
  • itemId: 사용자가 수정하거나 만들고 있는 스트림 항목의 ID입니다.
  • itemType: 사용자가 만들거나 수정 중인 스트림 항목의 유형으로, courseWork, courseWorkMaterial, announcement 중 하나입니다.
  • addOnToken: 특정 클래스룸 부가기능 작업을 승인하는 데 사용되는 토큰입니다.
  • login_hint: 현재 사용자의 Google ID입니다.

이 둘러보기에서는 login_hint를 다룹니다. 이 쿼리 매개변수가 제공되었는지 여부에 따라 사용자가 승인 흐름(누락된 경우) 또는 부가기능 검색 페이지(있는 경우)로 라우팅됩니다.

쿼리 매개변수에 액세스

쿼리 매개변수는 웹 애플리케이션에 URI 문자열로 전달됩니다. 이러한 값은 세션에 저장합니다. 이 값은 승인 흐름에서 사용되며 사용자에 대한 정보를 저장 및 검색하는 데 사용됩니다. 이러한 쿼리 매개변수는 부가기능을 처음 열 때만 전달됩니다.

Python

Flask 경로의 정의로 이동합니다 (제공된 예시를 따르는 경우 routes.py). 부가기능 방문 경로 맨 위에서(제공된 예에서는 /classroom-addon) login_hint 쿼리 매개변수를 검색하고 저장합니다.

# If the login_hint query parameter is available, we'll store it in the session.
if flask.request.args.get("login_hint"):
    flask.session["login_hint"] = flask.request.args.get("login_hint")

login_hint (있는 경우)가 세션에 저장되었는지 확인합니다. 이는 이러한 값을 저장하기에 적절한 장소입니다. 값은 임시적이며 부가기능이 열리면 새 값을 수신합니다.

# It's possible that we might return to this route later, in which case the
# parameters will not be passed in. Instead, use the values cached in the
# session.
login_hint = flask.session.get("login_hint")

# If there's still no login_hint query parameter, this must be their first
# time signing in, so send the user to the sign in page.
if login_hint is None:
    return start_auth_flow()

Java

컨트롤러 클래스(제공된 예에서는 AuthController.java/addon-discovery)에서 부가기능 시작 경로로 이동합니다. 이 경로의 시작 부분에서 login_hint 쿼리 매개변수를 검색하고 저장합니다.

/** Retrieve the login_hint or hd query parameters from the request URL. */
String login_hint = request.getParameter("login_hint");
String hd = request.getParameter("hd");

login_hint (있는 경우)가 세션에 저장되었는지 확인합니다. 이는 이러한 값을 저장하기에 적절한 장소입니다. 값은 임시적이며 부가기능이 열리면 새 값을 수신합니다.

/** If neither query parameter is sent, use the values in the session. */
if (login_hint == null && hd == null) {
    login_hint = (String) session.getAttribute("login_hint");
    hd = (String) session.getAttribute("hd");
}

/** If the hd query parameter is provided, add hd to the session and route
*   the user to the authorization page. */
else if (hd != null) {
    session.setAttribute("hd", hd);
    return startAuthFlow(model);
}

/** If the login_hint query parameter is provided, add it to the session. */
else if (login_hint != null) {
    session.setAttribute("login_hint", login_hint);
}

승인 흐름에 쿼리 매개변수 추가

login_hint 매개변수도 Google의 인증 서버에 전달되어야 합니다. 이렇게 하면 인증 프로세스가 용이해집니다. 애플리케이션이 인증을 시도하는 사용자를 알고 있는 경우, 서버는 힌트를 사용하여 로그인 양식의 이메일 필드를 미리 채워 로그인 흐름을 간소화합니다.

Python

Flask 서버 파일 (제공된 예시의 /authorize)에서 승인 경로로 이동합니다. flow.authorization_url 호출에 login_hint 인수를 추가합니다.

authorization_url, state = flow.authorization_url(
    # Enable offline access so that you can refresh an access token without
    # re-prompting the user for permission. Recommended for web server apps.
    access_type="offline",
    # Enable incremental authorization. Recommended as a best practice.
    include_granted_scopes="true",
    # The user will automatically be selected if we have the login_hint.
    login_hint=flask.session.get("login_hint"),

Java

AuthService.java 클래스의 authorize() 메서드로 이동합니다. login_hinthd를 메서드에 매개변수로 추가하고 login_hinthd 인수를 승인 URL 빌더에 추가합니다.

String authUrl = flow
    .newAuthorizationUrl()
    .setState(state)
    .set("login_hint", login_hint)
    .set("hd", hd)
    .setRedirectUri(REDIRECT_URI)
    .build();

사용자 인증 정보용 영구 스토리지 추가

부가기능이 로드될 때 login_hint가 쿼리 매개변수로 수신되면 사용자가 이미 애플리케이션의 승인 과정을 완료했음을 나타냅니다. 사용자가 다시 로그인하도록 강제하는 대신 이전 사용자 인증 정보를 가져와야 합니다.

승인 흐름이 완료되면 갱신 토큰이 수신되었습니다. 이 토큰을 저장합니다. 이 토큰을 사용하면 수명이 짧고 Google API를 사용하는 데 필요한 액세스 토큰을 얻는 데 다시 사용할 수 있습니다. 이전에 이러한 사용자 인증 정보를 세션에 저장했지만 재방문을 처리하려면 사용자 인증 정보를 저장해야 합니다.

사용자 스키마 정의 및 데이터베이스 설정

User의 데이터베이스 스키마를 설정합니다.

Python

사용자 스키마 정의

User에는 다음 속성이 포함됩니다.

  • id: 사용자의 Google ID입니다. 이 값은 login_hint 쿼리 매개변수에 제공된 값과 일치해야 합니다.
  • display_name: 사용자의 성과 이름(예: '홍길동')
  • email: 사용자의 이메일 주소입니다.
  • portrait_url: 사용자 프로필 사진의 URL입니다.
  • refresh_token: 이전에 획득한 갱신 토큰입니다.

이 예에서는 기본적으로 Python에서 지원하는 SQLite를 사용하여 저장소를 구현합니다. flask_sqlalchemy 모듈을 사용하여 데이터베이스 관리를 용이하게 합니다.

데이터베이스 설정

먼저 데이터베이스의 파일 위치를 지정합니다. 서버 구성 파일 (제공된 예에서는 config.py)으로 이동하여 다음을 추가합니다.

import os

# Point to a database file in the project root.
DATABASE_FILE_NAME = os.path.join(
    os.path.abspath(os.path.dirname(__file__)), 'data.sqlite')

class Config(object):
    SQLALCHEMY_DATABASE_URI = f"sqlite:///{DATABASE_FILE_NAME}"
    SQLALCHEMY_TRACK_MODIFICATIONS = False

그러면 Flask가 main.py 파일과 동일한 디렉터리에 있는 data.sqlite 파일을 가리킵니다.

다음으로 모듈 디렉터리로 이동하여 새 models.py 파일을 만듭니다. 제공된 예를 따르는 경우 webapp/models.py입니다. 새 파일에 다음을 추가하여 User 테이블을 정의합니다. 다른 경우 모듈 이름을 webapp로 대체합니다.

from webapp import db

# Database model to represent a user.
class User(db.Model):
    # The user's identifying information:
    id = db.Column(db.String(120), primary_key=True)
    display_name = db.Column(db.String(80))
    email = db.Column(db.String(120), unique=True)
    portrait_url = db.Column(db.Text())

    # The user's refresh token, which will be used to obtain an access token.
    # Note that refresh tokens will become invalid if:
    # - The refresh token has not been used for six months.
    # - The user revokes your app's access permissions.
    # - The user changes passwords.
    # - The user belongs to a Google Cloud organization
    #   that has session control policies in effect.
    refresh_token = db.Column(db.Text())

마지막으로 모듈의 __init__.py 파일에 다음을 추가하여 새 모델을 가져오고 데이터베이스를 만듭니다.

from webapp import models
from os import path
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy(app)

# Initialize the database file if not created.
if not path.exists(config.DATABASE_FILE_NAME):
    db.create_all()

Java

사용자 스키마 정의

User에는 다음 속성이 포함됩니다.

  • id: 사용자의 Google ID입니다. 이 값은 login_hint 쿼리 매개변수에 제공된 값과 일치해야 합니다.
  • email: 사용자의 이메일 주소입니다.

모듈의 resources 디렉터리에 schema.sql 파일을 생성합니다. Spring은 이 파일을 읽고 그에 따라 데이터베이스의 스키마를 생성합니다. 테이블 이름, users, User 속성(id, email)을 나타내는 열로 테이블을 정의합니다.

CREATE TABLE IF NOT EXISTS users (
    id VARCHAR(255) PRIMARY KEY, -- user's unique Google ID
    email VARCHAR(255), -- user's email address
);

Java 클래스를 만들어 데이터베이스의 User 모델을 정의합니다. 제공된 예에서는 User.java입니다.

@Entity 주석을 추가하여 데이터베이스에 저장할 수 있는 POJO임을 나타냅니다. schema.sql에서 구성한 해당 테이블 이름과 함께 @Table 주석을 추가합니다.

코드 예에는 두 속성의 생성자와 setter가 포함되어 있습니다. 생성자와 setter는 AuthController.java에서 데이터베이스에서 사용자를 만들거나 업데이트하는 데 사용됩니다. 필요에 따라 getter와 toString 메서드를 포함할 수도 있지만, 이 특정 둘러보기에서는 이러한 메서드가 사용되지 않으며 간결성을 위해 이 페이지의 코드 예에서 생략됩니다.

/** An entity class that provides a model to store user information. */
@Entity
@Table(name = "users")
public class User {
    /** The user's unique Google ID. The @Id annotation specifies that this
     *   is the primary key. */
    @Id
    @Column
    private String id;

    /** The user's email address. */
    @Column
    private String email;

    /** Required User class no args constructor. */
    public User() {
    }

    /** The User class constructor that creates a User object with the
    *   specified parameters.
    *   @param id the user's unique Google ID
    *   @param email the user's email address
    */
    public User(String id, String email) {
        this.id = id;
        this.email = email;
    }

    public void setId(String id) { this.id = id; }

    public void setEmail(String email) { this.email = email; }
}

UserRepository.java라는 인터페이스를 만들어 데이터베이스에 대한 CRUD 작업을 처리합니다. 이 인터페이스는 CrudRepository 인터페이스를 확장합니다.

/** Provides CRUD operations for the User class by extending the
 *   CrudRepository interface. */
@Repository
public interface UserRepository extends CrudRepository<User, String> {
}

컨트롤러 클래스는 클라이언트와 저장소 간의 통신을 용이하게 합니다. 따라서 컨트롤러 클래스 생성자를 업데이트하여 UserRepository 클래스를 삽입해야 합니다.

/** Declare UserRepository to be used in the Controller class constructor. */
private final UserRepository userRepository;

/**
*   ...
*   @param userRepository the class that interacts with User objects stored in
*   persistent storage.
*/
public AuthController(AuthService authService, UserRepository userRepository) {
    this.authService = authService;
    this.userRepository = userRepository;
}

데이터베이스 설정

사용자 관련 정보를 저장하려면 Spring Boot에서 기본적으로 지원되는 H2 데이터베이스를 사용합니다. 이 데이터베이스는 다른 클래스룸 관련 정보를 저장하기 위한 후속 둘러보기에서도 사용됩니다. H2 데이터베이스를 설정하려면 다음 구성을 application.properties에 추가해야 합니다.

# Enable configuration for persistent storage using an H2 database
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:file:./h2/userdb
spring.datasource.username=<USERNAME>
spring.datasource.password=<PASSWORD>
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false

spring.datasource.url 구성은 userdb 파일이 내부에 저장된 h2라는 디렉터리를 만듭니다. H2 데이터베이스 경로를 .gitignore에 추가합니다. 원하는 사용자 이름과 비밀번호로 데이터베이스를 설정하도록 애플리케이션을 실행하기 전에 spring.datasource.usernamespring.datasource.password를 업데이트해야 합니다. 애플리케이션을 실행한 후 데이터베이스의 사용자 이름과 비밀번호를 업데이트하려면 생성된 h2 디렉터리를 삭제하고 구성을 업데이트한 후 애플리케이션을 다시 실행합니다.

spring.jpa.hibernate.ddl-auto 구성을 update로 설정하면 애플리케이션이 다시 시작될 때 데이터베이스에 저장된 데이터가 보존됩니다. 애플리케이션이 다시 시작될 때마다 데이터베이스를 지우려면 이 구성을 create로 설정합니다.

spring.jpa.open-in-view 구성을 false로 설정합니다. 이 구성은 기본적으로 사용 설정되며 프로덕션에서 진단하기 어려운 성능 문제를 유발하는 것으로 알려져 있습니다.

앞에서 설명한 것처럼 반복 사용자의 사용자 인증 정보를 검색할 수 있어야 합니다. 이는 GoogleAuthorizationCodeFlow에서 제공하는 기본 제공 사용자 인증 정보 저장소 지원을 통해 촉진됩니다.

AuthService.java 클래스에서 사용자 인증 정보 클래스가 저장된 파일의 경로를 정의합니다. 이 예시에서는 파일이 /credentialStore 디렉터리에 생성됩니다. 사용자 인증 정보 저장소 경로를 .gitignore에 추가합니다. 이 디렉터리는 사용자가 승인 흐름을 시작하면 생성됩니다.

private static final File dataDirectory = new File("credentialStore");

그런 다음 AuthService.java 파일에 FileDataStoreFactory 객체를 만들고 반환하는 메서드를 만듭니다. 사용자 인증 정보를 저장하는 데이터 스토어입니다.

/** Creates and returns FileDataStoreFactory object to store credentials.
 *   @return FileDataStoreFactory dataStore used to save and obtain users ids
 *   mapped to Credentials.
 *   @throws IOException if creating the dataStore is unsuccessful.
 */
public FileDataStoreFactory getCredentialDataStore() throws IOException {
    FileDataStoreFactory dataStore = new FileDataStoreFactory(dataDirectory);
    return dataStore;
}

GoogleAuthorizationCodeFlow Builder() 메서드에 setDataStoreFactory를 포함하도록 AuthService.javagetFlow() 메서드를 업데이트하고 getCredentialDataStore()를 호출하여 Datastore를 설정합니다.

GoogleAuthorizationCodeFlow authorizationCodeFlow =
    new GoogleAuthorizationCodeFlow.Builder(
        HTTP_TRANSPORT,
        JSON_FACTORY,
        getClientSecrets(),
        getScopes())
    .setAccessType("offline")
    .setDataStoreFactory(getCredentialDataStore())
    .build();

다음으로, getAndSaveCredentials(String authorizationCode) 메서드를 업데이트합니다. 이전에는 이 방법을 사용하여 사용자 인증 정보를 어디에도 저장하지 않고 얻었습니다. 사용자 ID로 색인이 생성된 데이터 스토어에 사용자 인증 정보를 저장하도록 메서드를 업데이트합니다.

사용자 ID는 id_token를 사용하여 TokenResponse 객체에서 얻을 수 있지만 먼저 확인이 되어야 합니다. 그러지 않으면 클라이언트 애플리케이션이 수정된 사용자 ID를 서버에 전송하여 사용자를 가장할 수 있습니다. Google API 클라이언트 라이브러리를 사용하여 id_token의 유효성을 검사하는 것이 좋습니다. 자세한 내용은 [Google ID 토큰 확인에 대한 Google ID 페이지] 를 참조하세요.

// Obtaining the id_token will help determine which user signed in to the application.
String idTokenString = tokenResponse.get("id_token").toString();

// Validate the id_token using the GoogleIdTokenVerifier object.
GoogleIdTokenVerifier googleIdTokenVerifier = new GoogleIdTokenVerifier.Builder(
        HTTP_TRANSPORT,
        JSON_FACTORY)
    .setAudience(Collections.singletonList(
        googleClientSecrets.getWeb().getClientId()))
    .build();

GoogleIdToken idToken = googleIdTokenVerifier.verify(idTokenString);

if (idToken == null) {
    throw new Exception("Invalid ID token.");
}

id_token가 확인되면 획득한 사용자 인증 정보와 함께 저장할 userId를 가져옵니다.

// Obtain the user id from the id_token.
Payload payload = idToken.getPayload();
String userId = payload.getSubject();

userId를 포함하도록 flow.createAndStoreCredential 호출을 업데이트합니다.

// Save the user id and credentials to the configured FileDataStoreFactory.
Credential credential = flow.createAndStoreCredential(tokenResponse, userId);

특정 사용자의 사용자 인증 정보를 반환하는 메서드를 AuthService.java 클래스에 추가합니다(데이터 저장소에 있는 경우).

/** Find credentials in the datastore based on a specific user id.
*   @param userId key to find in the file datastore.
*   @return Credential object to be returned if a matching key is found in the datastore. Null if
*   the key doesn't exist.
*   @throws Exception if building flow object or checking for userId key is unsuccessful. */
public Credential loadFromCredentialDataStore(String userId) throws Exception {
    try {
        GoogleAuthorizationCodeFlow flow = getFlow();
        Credential credential = flow.loadCredential(userId);
        return credential;
    } catch (Exception e) {
        e.printStackTrace();
        throw e;
    }
}

사용자 인증 정보 가져오기

Users를 가져오는 메서드를 정의합니다. 특정 사용자 레코드를 검색하는 데 사용할 수 있는 idlogin_hint 쿼리 매개변수에 제공됩니다.

Python

def get_credentials_from_storage(id):
    """
    Retrieves credentials from the storage and returns them as a dictionary.
    """
    return User.query.get(id)

Java

AuthController.java 클래스에서 사용자 ID를 기준으로 데이터베이스에서 사용자를 검색하는 메서드를 정의합니다.

/** Retrieves stored credentials based on the user id.
*   @param id the id of the current user
*   @return User the database entry corresponding to the current user or null
*   if the user doesn't exist in the database.
*/
public User getUser(String id) {
    if (id != null) {
        Optional<User> user = userRepository.findById(id);
        if (user.isPresent()) {
            return user.get();
        }
    }
    return null;
}

사용자 인증 정보 저장

사용자 인증 정보를 저장하는 데는 두 가지 시나리오가 있습니다. 사용자의 id가 이미 데이터베이스에 있으면 기존 레코드를 새 값으로 업데이트합니다. 그렇지 않으면 새 User 레코드를 만들어 데이터베이스에 추가합니다.

Python

먼저 저장 또는 업데이트 동작을 구현하는 유틸리티 메서드를 정의합니다.

def save_user_credentials(credentials=None, user_info=None):
    """
    Updates or adds a User to the database. A new user is added only if both
    credentials and user_info are provided.

    Args:
        credentials: An optional Credentials object.
        user_info: An optional dict containing user info returned by the
            OAuth 2.0 API.
    """

    existing_user = get_credentials_from_storage(
        flask.session.get("login_hint"))

    if existing_user:
        if user_info:
            existing_user.id = user_info.get("id")
            existing_user.display_name = user_info.get("name")
            existing_user.email = user_info.get("email")
            existing_user.portrait_url = user_info.get("picture")

        if credentials and credentials.refresh_token is not None:
            existing_user.refresh_token = credentials.refresh_token

    elif credentials and user_info:
        new_user = User(id=user_info.get("id"),
                        display_name=user_info.get("name"),
                        email=user_info.get("email"),
                        portrait_url=user_info.get("picture"),
                        refresh_token=credentials.refresh_token)

        db.session.add(new_user)

    db.session.commit()

사용자 인증 정보를 데이터베이스에 저장할 수 있는 경우는 두 가지가 있습니다. 승인 흐름이 끝날 때 사용자가 애플리케이션으로 돌아올 때와 API 호출을 실행할 때입니다. 이전에 세션 credentials 키를 설정한 위치입니다.

callback 경로의 끝에 있는 save_user_credentials에 전화를 겁니다. 사용자 이름만 추출하는 대신 user_info 객체를 유지합니다.

# The flow is complete! We'll use the credentials to fetch the user's info.
user_info_service = googleapiclient.discovery.build(
    serviceName="oauth2", version="v2", credentials=credentials)

user_info = user_info_service.userinfo().get().execute()

flask.session["username"] = user_info.get("name")

save_user_credentials(credentials, user_info)

또한 API 호출 후 사용자 인증 정보를 업데이트해야 합니다. 이 경우 업데이트된 사용자 인증 정보를 save_user_credentials 메서드에 인수로 제공할 수 있습니다.

# Save credentials in case access token was refreshed.
flask.session["credentials"] = credentials_to_dict(credentials)
save_user_credentials(credentials)

Java

먼저 H2 데이터베이스에 User 객체를 저장하거나 업데이트하는 메서드를 정의합니다.

/** Adds or updates a user in the database.
*   @param credential the credentials object to save or update in the database.
*   @param userinfo the userinfo object to save or update in the database.
*   @param session the current session.
*/
public void saveUser(Credential credential, Userinfo userinfo, HttpSession session) {
    User storedUser = null;
    if (session != null && session.getAttribute("login_hint") != null) {
        storedUser = getUser(session.getAttribute("login_hint").toString());
    }

    if (storedUser != null) {
        if (userinfo != null) {
            storedUser.setId(userinfo.getId());
            storedUser.setEmail(userinfo.getEmail());
        }
        userRepository.save(storedUser);
    } else if (credential != null && userinfo != null) {
        User newUser = new User(
            userinfo.getId(),
            userinfo.getEmail(),
        );
        userRepository.save(newUser);
    }
}

사용자 인증 정보를 데이터베이스에 저장할 수 있는 경우는 두 가지가 있습니다. 승인 흐름이 끝날 때 사용자가 애플리케이션으로 돌아올 때와 API 호출을 실행할 때입니다. 이전에 세션 credentials 키를 설정한 위치입니다.

/callback 경로 끝에서 saveUser를 호출합니다. 사용자의 이메일을 단순히 추출하는 대신 user_info 객체를 유지해야 합니다.

/** This is the end of the auth flow. We should save user info to the database. */
Userinfo userinfo = authService.getUserInfo(credentials);
saveUser(credentials, userinfo, session);

또한 API 호출 후 사용자 인증 정보를 업데이트해야 합니다. 이 경우 업데이트된 사용자 인증 정보를 saveUser 메서드에 인수로 제공할 수 있습니다.

/** Save credentials in case access token was refreshed. */
saveUser(credentials, null, session);

만료된 사용자 인증 정보

갱신 토큰이 유효하지 않게 되는 데는 몇 가지 이유가 있습니다. 예를 들면 다음과 같습니다.

  • 갱신 토큰이 6개월 동안 사용되지 않았습니다.
  • 사용자가 앱의 액세스 권한을 취소하는 경우
  • 사용자가 비밀번호를 변경합니다.
  • 사용자가 세션 제어 정책이 적용되는 Google Cloud 조직에 속합니다.

사용자 인증 정보가 무효화된 경우 사용자를 다시 승인 흐름을 통해 전송하여 새 토큰을 획득합니다.

사용자 자동 라우팅

사용자가 이전에 애플리케이션을 승인했는지 감지하도록 부가기능 시작 경로를 수정합니다. 그렇다면 Google의 기본 부가기능 페이지로 연결하세요. 그렇지 않은 경우 로그인하라는 메시지를 표시하세요.

Python

애플리케이션이 시작될 때 데이터베이스 파일이 생성되었는지 확인합니다. 모듈 이니셜라이저 (예: 제공된 예의 webapp/__init__.py) 또는 서버를 실행하는 기본 메서드에 다음을 삽입합니다.

# Initialize the database file if not created.
if not os.path.exists(DATABASE_FILE_NAME):
    db.create_all()

그러면 메서드는 위에서 설명한 대로 login_hint 쿼리 매개변수를 처리해야 합니다. 그런 다음 재방문자인 경우 스토어 사용자 인증 정보를 로드합니다. login_hint을(를) 수신했다면 재방문자입니다. 이 사용자의 저장된 사용자 인증 정보를 가져와 세션에 로드합니다.

stored_credentials = get_credentials_from_storage(login_hint)

# If we have stored credentials, store them in the session.
if stored_credentials:
    # Load the client secrets file contents.
    client_secrets_dict = json.load(
        open(CLIENT_SECRETS_FILE)).get("web")

    # Update the credentials in the session.
    if not flask.session.get("credentials"):
        flask.session["credentials"] = {}

    flask.session["credentials"] = {
        "token": stored_credentials.access_token,
        "refresh_token": stored_credentials.refresh_token,
        "token_uri": client_secrets_dict["token_uri"],
        "client_id": client_secrets_dict["client_id"],
        "client_secret": client_secrets_dict["client_secret"],
        "scopes": SCOPES
    }

    # Set the username in the session.
    flask.session["username"] = stored_credentials.display_name

마지막으로 사용자 인증 정보가 없으면 사용자를 로그인 페이지로 라우팅합니다. 액세스할 수 있는 경우 기본 부가기능 페이지로 라우팅합니다.

if "credentials" not in flask.session or \
    flask.session["credentials"]["refresh_token"] is None:
    return flask.render_template("authorization.html")

return flask.render_template(
    "addon-discovery.html",
    message="You've reached the addon discovery page.")

Java

부가기능 시작 경로로 이동합니다 (제공된 예의 /addon-discovery). 위에서 설명한 대로 여기에서 login_hint 쿼리 매개변수를 처리했습니다.

먼저 세션에 사용자 인증 정보가 존재하는지 확인합니다. 권한이 없으면 startAuthFlow 메서드를 호출하여 사용자를 인증 흐름을 통해 라우팅합니다.

/** Check if the credentials exist in the session. The session could have
 *   been cleared when the user clicked the Sign-Out button, and the expected
 *   behavior after sign-out would be to display the sign-in page when the
 *   iframe is opened again. */
if (session.getAttribute("credentials") == null) {
    return startAuthFlow(model);
}

그런 다음 재방문자인 경우 H2 데이터베이스에서 사용자를 로드합니다. login_hint 쿼리 매개변수를 수신하면 재방문자입니다. 사용자가 H2 데이터베이스에 존재하는 경우 이전에 설정한 사용자 인증 정보 데이터 저장소에서 사용자 인증 정보를 로드하고 세션에서 사용자 인증 정보를 설정합니다. 사용자 인증 정보 Datastore에서 사용자 인증 정보를 가져오지 않은 경우 startAuthFlow를 호출하여 사용자를 인증 흐름을 통해 라우팅합니다.

/** At this point, we know that credentials exist in the session, but we
 *   should update the session credentials with the credentials in persistent
 *   storage in case they were refreshed. If the credentials in persistent
 *   storage are null, we should navigate the user to the authorization flow
 *   to obtain persisted credentials. */

User storedUser = getUser(login_hint);

if (storedUser != null) {
    Credential credential = authService.loadFromCredentialDataStore(login_hint);
    if (credential != null) {
        session.setAttribute("credentials", credential);
    } else {
        return startAuthFlow(model);
    }
}

마지막으로 사용자를 부가기능 방문 페이지로 라우트합니다.

/** Finally, if there are credentials in the session and in persistent
 *   storage, direct the user to the addon-discovery page. */
return "addon-discovery";

부가기능 테스트

Google 클래스룸교사 테스트 사용자 중 한 명으로 로그인합니다. 수업 과제 탭으로 이동하여 새 과제를 만듭니다. 텍스트 영역 아래의 부가기능 버튼을 클릭한 후 부가기능을 선택합니다. iframe이 열리고 부가기능이 Google Workspace Marketplace SDK의 앱 구성 페이지에서 지정한 연결 설정 URI를 로드합니다.

수고하셨습니다 이제 다음 단계인 첨부파일 만들기 및 사용자 역할 식별으로 진행할 준비가 되었습니다.