Menangani login berulang

Ini adalah panduan ketiga dalam seri panduan add-on Classroom.

Dalam panduan ini, Anda menangani kunjungan berulang ke add-on kami dengan otomatis mengambil kredensial yang diberikan sebelumnya oleh pengguna. Kemudian, Anda akan mengarahkan pengguna ke halaman tempat mereka dapat segera mengeluarkan permintaan API. Ini adalah perilaku yang diperlukan untuk add-on Classroom.

Dalam panduan ini, Anda akan menyelesaikan langkah-langkah berikut:

  • Implementasikan penyimpanan persisten untuk kredensial pengguna kami.
  • Ambil dan evaluasi parameter kueri add-on berikut:
    • login_hint: Nomor ID Google pengguna yang login.
    • hd: Domain pengguna yang login.

Perhatikan bahwa hanya satu pesan berikut yang dikirim. Classroom API akan mengirimkan parameter hd jika pengguna BELUM memberi otorisasi pada aplikasi Anda. Jika tidak, API akan mengirim login_hint. Daftar lengkap parameter kueri tersedia di halaman panduan iframe.

Setelah selesai, Anda dapat sepenuhnya mengizinkan pengguna di aplikasi web Anda dan melakukan panggilan ke Google API.

Memahami parameter kueri iframe

Classroom akan memuat URI Penyiapan Lampiran add-on Anda saat dibuka. Classroom menambahkan beberapa parameter kueri GET ke URI; parameter ini berisi informasi kontekstual yang berguna. Misalnya, jika URI Penemuan Lampiran Anda adalah https://example.com/addon, Classroom akan membuat iframe dengan URL sumber yang disetel ke https://example.com/addon?courseId=XXX&postId=YYY&addOnToken=ZZZ, dengan XXX, YYY, dan ZZZ sebagai ID string. Lihat panduan iframe untuk deskripsi mendetail tentang skenario ini.

Ada lima kemungkinan parameter kueri untuk URL discovery:

  • courseId: ID kursus Classroom saat ini.
  • postId: ID postingan tugas yang diedit atau dibuat oleh pengguna.
  • addOnToken: Token yang digunakan untuk mengizinkan tindakan add-on Classroom tertentu.
  • login_hint: ID Google pengguna saat ini.
  • hd: Domain host untuk pengguna saat ini, seperti example.com.

Panduan ini membahas hd dan login_hint. Pengguna diarahkan berdasarkan parameter kueri mana pun yang diberikan, baik ke alur otorisasi jika hd, atau ke halaman penemuan add-on jika login_hint.

Mengakses parameter kueri

Seperti yang dijelaskan di atas, parameter kueri diteruskan ke aplikasi web Anda dalam string URI. Simpan nilai ini dalam sesi Anda; nilai ini akan digunakan dalam alur otorisasi serta untuk menyimpan dan mengambil informasi tentang pengguna. Parameter kueri ini hanya diteruskan saat add-on pertama kali dibuka.

Python

Buka definisi rute Flask Anda (routes.py jika Anda mengikuti contoh yang kami sediakan). Di bagian atas rute landing add-on Anda (/classroom-addon dalam contoh yang tersedia), ambil dan simpan parameter kueri login_hint dan hd:

# Retrieve the login_hint and hd query parameters.
login_hint = flask.request.args.get("login_hint")
hd = flask.request.args.get("hd")

Pastikan login_hint dan hd disimpan dalam sesi. Ini adalah tempat yang tepat untuk menyimpan nilai ini. Nilai tersebut bersifat sementara dan Anda akan menerima nilai baru saat add-on dibuka.

# 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.

# If neither query parameter is available, use the values in the session.
if login_hint is None and hd is None:
    login_hint = flask.session.get("login_hint")
    hd = flask.session.get("hd")

# If there's no login_hint query parameter, then check for hd.
# Send the user to the sign in page.
elif hd is not None:
    flask.session["hd"] = hd
    return start_auth_flow()

# If the login_hint query parameter is available, we'll store it in the
# session.
else:
    flask.session["login_hint"] = login_hint

Java

Buka rute landing add-on di class pengontrol Anda (/addon-discovery dalam AuthController.java pada contoh yang diberikan). Di awal rute ini, ambil dan simpan parameter kueri login_hint dan hd.

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

Pastikan login_hint dan hd disimpan dalam sesi. Ini adalah tempat yang tepat untuk menyimpan nilai ini. Nilai tersebut bersifat sementara dan Anda akan menerima nilai baru saat add-on dibuka.

/** 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);
}

Menambahkan parameter kueri ke alur otorisasi

Parameter login_hint dan hd juga harus diteruskan ke server autentikasi Google. Hal ini memudahkan proses autentikasi. Jika aplikasi Anda mengetahui pengguna mana yang mencoba mengautentikasi, server akan menggunakan petunjuk untuk menyederhanakan alur login dengan mengisi otomatis kolom email di formulir login.

Python

Buka rute otorisasi di file server Flask Anda (/authorize dalam contoh yang kami sediakan). Tambahkan argumen login_hint dan hd ke panggilan ke flow.authorization_url.

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"),
    # If we don't have login_hint, passing hd will reduce the list of
    # accounts in the account chooser to only those with the same domain.
    hd=flask.session.get("hd"))

Java

Buka metode authorize() di class AuthService.java. Tambahkan login_hint dan hd sebagai parameter ke metode, dan tambahkan argumen login_hint dan hd ke pembuat URL otorisasi.

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

Menambahkan penyimpanan persisten untuk kredensial pengguna

Jika Anda menerima login_hint sebagai parameter kueri saat add-on dimuat, itu adalah indikasi bahwa pengguna telah menyelesaikan alur otorisasi untuk aplikasi kita. Anda harus mengambil kredensial sebelumnya, bukan memaksa mereka untuk login lagi.

Ingat bahwa Anda menerima token refresh setelah menyelesaikan alur otorisasi. Simpan token ini; dan digunakan kembali untuk mendapatkan token akses, yang berumur pendek dan diperlukan untuk menggunakan Google API. Anda sebelumnya telah menyimpan kredensial ini dalam sesi, tetapi Anda perlu menyimpan kredensial tersebut untuk menangani kunjungan berulang.

Mendefinisikan skema Pengguna dan menyiapkan database

Siapkan skema database untuk User.

Python

Menentukan Skema pengguna

User berisi atribut berikut:

  • id: ID Google pengguna. Nilai ini harus cocok dengan nilai yang diberikan dalam parameter kueri login_hint.
  • display_name: Nama depan dan belakang pengguna, seperti "Alex Smith".
  • email: Alamat email pengguna.
  • portrait_url: URL foto profil pengguna.
  • refresh_token: Token refresh yang sebelumnya diperoleh.

Contoh ini mengimplementasikan penyimpanan menggunakan SQLite, yang secara native didukung oleh Python. Class ini menggunakan modul flask_sqlalchemy untuk memfasilitasi pengelolaan database.

Menyiapkan database

Pertama, tentukan lokasi file untuk database kita. Buka file konfigurasi server Anda (config.py dalam contoh yang tersedia) dan tambahkan kode berikut.

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

Tindakan ini akan mengarahkan Flask ke file data.sqlite dalam direktori yang sama dengan file main.py Anda.

Selanjutnya, buka direktori modul Anda dan buat file models.py baru. Ini adalah webapp/models.py jika Anda mengikuti contoh yang kami berikan. Tambahkan kode berikut ke file baru untuk menentukan tabel User, dengan mengganti nama modul dengan webapp jika berbeda.

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())

Terakhir, dalam file __init__.py modul Anda, tambahkan kode berikut untuk mengimpor model baru dan membuat database.

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

Menentukan Skema pengguna

User berisi atribut berikut:

  • id: ID Google pengguna. Ini harus cocok dengan nilai yang diberikan dalam parameter kueri login_hint.
  • email: Alamat email pengguna.

Buat file schema.sql dalam direktori resources modul. Spring akan membaca file ini dan menghasilkan skema untuk database yang sesuai. Tentukan tabel dengan nama tabel, users, dan kolom untuk mewakili atribut User, id, dan email.

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

Buat class Java untuk menentukan model User untuk database. Ini adalah User.java dalam contoh yang diberikan.

Tambahkan anotasi @Entity untuk menunjukkan bahwa ini adalah POJO yang dapat disimpan ke database. Tambahkan anotasi @Table dengan nama tabel yang sesuai yang Anda konfigurasikan di schema.sql.

Perhatikan bahwa contoh kode menyertakan konstruktor dan penyetel untuk kedua atribut. Konstruktor dan penyetel digunakan dalam AuthController.java untuk membuat atau memperbarui pengguna dalam database. Anda juga dapat menyertakan pengambil dan metode toString jika dirasa sesuai, tetapi untuk panduan khusus ini, metode ini tidak digunakan dan dihilangkan dari contoh kode di halaman ini agar lebih singkat.

/** 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; }
}

Buat antarmuka yang disebut UserRepository.java untuk menangani operasi CRUD ke database. Antarmuka ini memperluas antarmuka CrudRepository.

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

Class pengontrol memfasilitasi komunikasi antara klien dan repositori. Oleh karena itu, update konstruktor class pengontrol untuk memasukkan class 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;
}

Menyiapkan database

Untuk menyimpan informasi terkait Pengguna, gunakan database H2 yang secara intrinsik didukung di Spring Boot. Database ini juga digunakan dalam panduan berikutnya untuk menyimpan informasi lain yang terkait dengan Classroom. Penyiapan database H2 memerlukan penambahan konfigurasi berikut ke 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

Konfigurasi spring.datasource.url membuat direktori bernama h2, dengan file userdb disimpan di dalamnya. Tambahkan jalur ke database H2 ke .gitignore. Anda harus mengupdate spring.datasource.username dan spring.datasource.password sebelum menjalankan aplikasi untuk menetapkan database dengan nama pengguna dan sandi pilihan Anda. Untuk memperbarui nama pengguna dan sandi untuk database setelah menjalankan aplikasi, hapus direktori h2 yang dihasilkan, perbarui konfigurasi, dan jalankan ulang aplikasi.

Menyetel konfigurasi spring.jpa.hibernate.ddl-auto ke update akan memastikan bahwa data yang disimpan dalam database dipertahankan saat aplikasi dimulai ulang. Untuk menghapus database setiap kali aplikasi dimulai ulang, tetapkan konfigurasi ini ke create.

Setel konfigurasi spring.jpa.open-in-view ke false. Konfigurasi ini diaktifkan secara default dan dapat diketahui dapat mengakibatkan masalah performa yang sulit didiagnosis dalam produksi.

Seperti yang dijelaskan sebelumnya, Anda harus dapat mengambil kredensial pengguna berulang. Hal ini difasilitasi oleh dukungan penyimpanan kredensial bawaan yang ditawarkan oleh GoogleAuthorizationCodeFlow.

Di class AuthService.java, tentukan jalur ke file tempat class kredensial disimpan. Dalam contoh ini, file dibuat di direktori /credentialStore. Tambahkan jalur ke penyimpanan kredensial ke .gitignore. Direktori ini dibuat setelah pengguna memulai alur otorisasi.

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

Selanjutnya, buat metode dalam file AuthService.java yang membuat dan menampilkan objek FileDataStoreFactory. Ini adalah datastore yang menyimpan kredensial.

/** 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;
}

Perbarui metode getFlow() di AuthService.java untuk menyertakan setDataStoreFactory dalam metode GoogleAuthorizationCodeFlow Builder() dan panggil getCredentialDataStore() untuk menetapkan datastore.

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

Selanjutnya, perbarui metode getAndSaveCredentials(String authorizationCode). Sebelumnya, metode ini memperoleh kredensial tanpa menyimpannya di mana pun. Perbarui metode untuk menyimpan kredensial di datastore yang diindeks oleh ID pengguna.

User-ID dapat diperoleh dari objek TokenResponse menggunakan id_token, tetapi harus diverifikasi terlebih dahulu. Jika tidak, aplikasi klien mungkin dapat meniru identitas pengguna dengan mengirimkan ID pengguna yang dimodifikasi ke server. Sebaiknya gunakan library Klien Google API untuk memvalidasi id_token. Lihat [halaman Identitas Google tentang memverifikasi token ID Google] untuk mengetahui informasi selengkapnya.

// 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.");
}

Setelah id_token diverifikasi, dapatkan userId untuk disimpan bersama dengan kredensial yang diperoleh.

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

Perbarui panggilan ke flow.createAndStoreCredential untuk menyertakan userId.

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

Tambahkan metode ke class AuthService.java yang menampilkan kredensial untuk pengguna tertentu jika ada di datastore.

/** 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;
    }
}

Mengambil kredensial

Tentukan metode untuk mengambil Users. Anda diberi id dalam parameter kueri login_hint, yang dapat digunakan untuk mengambil data pengguna tertentu.

Python

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

Java

Di class AuthController.java, tentukan metode untuk mengambil pengguna dari database berdasarkan ID penggunanya.

/** 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;
}

Simpan kredensial

Ada dua skenario saat menyimpan kredensial. Jika id pengguna sudah ada dalam database, perbarui data yang ada dengan nilai baru. Jika tidak, buat data User baru dan tambahkan ke database.

Python

Pertama, tentukan metode utilitas yang menerapkan perilaku penyimpanan atau update.

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()

Ada dua instance yang dapat membuat Anda menyimpan kredensial ke database: saat pengguna kembali ke aplikasi Anda di akhir alur otorisasi dan saat melakukan panggilan API. Di sinilah kita sebelumnya menetapkan kunci credentials sesi.

Panggil save_user_credentials di akhir rute callback. Pertahankan objek user_info, bukan hanya mengekstrak nama pengguna.

# 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)

Anda juga harus memperbarui kredensial setelah memanggil API. Dalam hal ini, Anda dapat memberikan kredensial yang diperbarui sebagai argumen untuk metode save_user_credentials.

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

Java

Pertama-tama, tentukan metode yang menyimpan atau memperbarui objek User dalam database H2.

/** 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);
    }
}

Ada dua instance yang dapat membuat Anda menyimpan kredensial ke database: saat pengguna kembali ke aplikasi Anda di akhir alur otorisasi dan saat melakukan panggilan API. Di sinilah kita sebelumnya menetapkan kunci credentials sesi.

Panggil saveUser di akhir rute /callback. Anda harus mempertahankan objek user_info, bukan hanya mengekstrak email pengguna.

/** 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);

Anda juga harus memperbarui kredensial setelah memanggil API. Dalam hal ini, Anda dapat memberikan kredensial yang diperbarui sebagai argumen untuk metode saveUser.

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

Kredensial sudah tidak berlaku

Perhatikan, ada beberapa alasan mengapa token refresh menjadi tidak valid. Hal ini mencakup:

  • Token refresh tidak digunakan selama enam bulan.
  • Pengguna mencabut izin akses aplikasi Anda.
  • Pengguna mengubah sandi.
  • Pengguna ini adalah anggota organisasi Google Cloud yang memberlakukan kebijakan kontrol sesi.

Dapatkan token baru dengan mengarahkan pengguna melalui alur otorisasi lagi jika kredensial mereka ternyata tidak valid.

Mengarahkan pengguna secara otomatis

Mengubah rute landing add-on untuk mendeteksi apakah pengguna sebelumnya telah mengizinkan aplikasi kita. Jika demikian, arahkan mereka ke halaman add-on utama. Jika tidak, minta mereka untuk login.

Python

Pastikan file database sudah dibuat saat aplikasi diluncurkan. Masukkan kode berikut ke penginisialisasi modul (seperti webapp/__init__.py dalam contoh yang tersedia) atau dalam metode utama yang meluncurkan server.

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

Selanjutnya, metode Anda harus menangani parameter kueri login_hint dan hd seperti yang dibahas di atas. Selanjutnya, muat kredensial toko jika ini adalah pengunjung berulang. Anda tahu bahwa ini adalah pengunjung berulang jika Anda menerima login_hint. Ambil semua kredensial yang disimpan untuk pengguna ini dan muat ke dalam sesi.

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

Terakhir, arahkan pengguna ke halaman login jika kita tidak memiliki kredensial mereka. Jika ya, arahkan mereka ke halaman add-on utama.

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

Buka rute landing add-on Anda (/addon-discovery dalam contoh yang diberikan). Seperti yang dibahas di atas, di sinilah Anda menangani parameter kueri login_hint dan hd.

Pertama, periksa apakah kredensial ada dalam sesi tersebut. Jika tidak, arahkan pengguna melalui alur autentikasi dengan memanggil metode 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);
}

Kemudian, muat pengguna dari database H2 jika ini adalah pengunjung berulang. Ini adalah pengunjung berulang jika Anda menerima parameter kueri login_hint. Jika pengguna ada di database H2, muat kredensial dari kredensial datastore yang disiapkan sebelumnya, lalu setel kredensial tersebut dalam sesi. Jika kredensial tidak diperoleh dari datastore kredensial, arahkan pengguna melalui alur autentikasi dengan memanggil 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);
    }
}

Terakhir, arahkan pengguna ke halaman landing add-on.

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

Menguji add-on

Login ke Google Classroom sebagai salah satu pengguna uji coba Pengajar Anda. Buka tab Tugas Kelas dan buat Tugas baru. Klik tombol Add-ons di bawah area teks, lalu pilih add-on Anda. iframe akan terbuka dan add-on akan memuat URI Penyiapan Lampiran yang Anda tentukan di halaman App Configuration di GWM SDK.

Selamat! Anda siap untuk melanjutkan ke langkah berikutnya: membuat lampiran dan mengidentifikasi peran pengguna.