จัดการการเข้าสู่ระบบซ้ำ

นี่เป็นคำแนะนำแบบทีละขั้นที่ 3 ในชุดคำแนะนำแบบทีละขั้นเกี่ยวกับส่วนเสริมของ Classroom

ในคำแนะนำแบบทีละขั้นนี้ คุณจัดการการเข้าชมส่วนเสริมซ้ำโดยการดึงข้อมูลเข้าสู่ระบบที่ผู้ใช้เคยให้ไว้ก่อนหน้านี้โดยอัตโนมัติ จากนั้นคุณจะเปลี่ยนเส้นทางผู้ใช้ไปยังหน้าเว็บที่สามารถออกคำขอ API ได้ทันที ซึ่งเป็นลักษณะการทำงานที่จำเป็นสำหรับส่วนเสริมของ Classroom

ในคำแนะนำแบบทีละขั้นนี้ คุณจะต้องดำเนินการต่อไปนี้ให้เสร็จสมบูรณ์

  • ใช้พื้นที่เก็บข้อมูลถาวรสำหรับข้อมูลเข้าสู่ระบบของผู้ใช้
  • ดึงข้อมูลและประเมินพารามิเตอร์การค้นหาส่วนเสริม login_hint นี่คือหมายเลข Google ID ที่ไม่ซ้ำกันของผู้ใช้ที่ลงชื่อเข้าใช้

เมื่อดำเนินการเสร็จแล้ว คุณจะให้สิทธิ์ผู้ใช้ในเว็บแอปได้อย่างเต็มที่และออกการเรียกไปยัง Google APIs

ทำความเข้าใจพารามิเตอร์การค้นหาของ iframe

Classroom จะโหลด URI การตั้งค่าไฟล์แนบของส่วนเสริมเมื่อเปิดขึ้น Classroom จะเพิ่มพารามิเตอร์การค้นหา GET หลายรายการลงใน URI ซึ่งพารามิเตอร์เหล่านี้มีข้อมูลบริบทที่เป็นประโยชน์ ตัวอย่างเช่น หาก URI การค้นพบไฟล์แนบของคุณคือ https://example.com/addon Classroom จะสร้าง iframe โดยตั้งค่า URL แหล่งที่มาเป็น https://example.com/addon?courseId=XXX&itemId=YYY&itemType=courseWork&addOnToken=ZZZ โดยที่ XXX, YYY และ ZZZ เป็นรหัสสตริง ดูคู่มือ iframe สำหรับคำอธิบายโดยละเอียดของสถานการณ์นี้

พารามิเตอร์การค้นหาที่เป็นไปได้สําหรับ URL การค้นพบมี 5 รายการ ดังนี้

  • courseId: รหัสหลักสูตรปัจจุบันของ Classroom
  • itemId: รหัสของรายการสตรีมที่ผู้ใช้กำลังแก้ไขหรือสร้าง
  • itemType: ประเภทรายการสตรีมที่ผู้ใช้สร้างหรือแก้ไข ซึ่งได้แก่ courseWork, courseWorkMaterial หรือ announcement
  • addOnToken: โทเค็นที่ใช้เพื่อให้สิทธิ์การดำเนินการบางอย่างของส่วนเสริมของ Classroom
  • login_hint: รหัส Google ของผู้ใช้ปัจจุบัน

คำแนะนำแบบทีละขั้นนี้ที่อยู่ของ login_hint ระบบจะกำหนดเส้นทางผู้ใช้โดยอิงตามการระบุพารามิเตอร์การค้นหานี้ไปยังขั้นตอนการให้สิทธิ์ (หากไม่มี) หรือไปยังหน้าการค้นพบของส่วนเสริม (หากมี)

เข้าถึงพารามิเตอร์การค้นหา

ระบบจะส่งพารามิเตอร์การค้นหาไปยังเว็บแอปพลิเคชันในสตริง URI จัดเก็บค่าเหล่านี้ในเซสชันของคุณ ซึ่งจะใช้ในขั้นตอนการให้สิทธิ์และเพื่อจัดเก็บและเรียกข้อมูลเกี่ยวกับผู้ใช้ ระบบจะส่งพารามิเตอร์การค้นหาเหล่านี้ เมื่อมีการเปิดส่วนเสริมเป็นครั้งแรกเท่านั้น

Python

ไปที่คำจำกัดความของเส้นทาง Flask (routes.py ถ้าทำตามตัวอย่างที่ให้ไว้) ที่ด้านบนของเส้นทาง Landing Page ของส่วนเสริม (/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

ไปยังเส้นทาง Landing Page ของส่วนเสริมในคลาสตัวควบคุม (/addon-discovery ใน AuthController.java ในตัวอย่างที่ระบุ) ที่จุดเริ่มต้นของเส้นทางนี้ ให้ดึงข้อมูลและจัดเก็บพารามิเตอร์การค้นหา 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 ในตัวอย่างที่เราให้ไว้) เพิ่มอาร์กิวเมนต์ login_hint ในการเรียกไปยัง 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"),

Java

ไปที่เมธอด authorize() ในชั้นเรียน AuthService.java เพิ่ม login_hint และ hd เป็นพารามิเตอร์ลงในเมธอด แล้วเพิ่มอาร์กิวเมนต์ login_hint และ hd ลงในเครื่องมือสร้าง 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 ของผู้ใช้ ควรตรงกับค่าที่ระบุในพารามิเตอร์การค้นหา login_hint
  • display_name: ชื่อและนามสกุลของผู้ใช้ เช่น "สมชาย สกุลดี"
  • email: อีเมลของผู้ใช้
  • portrait_url: URL รูปโปรไฟล์ของผู้ใช้
  • refresh_token: โทเค็นการรีเฟรชที่ได้มาก่อนหน้านี้

ตัวอย่างนี้ใช้พื้นที่เก็บข้อมูลโดยใช้ SQLite ซึ่ง Python รองรับโดยค่าเริ่มต้น โดยใช้โมดูล 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 ไปยังไฟล์ data.sqlite ในไดเรกทอรีเดียวกับไฟล์ main.py

จากนั้น ไปที่ไดเรกทอรีโมดูลและสร้างไฟล์ 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 ของผู้ใช้ ค่านี้ควรตรงกับค่าที่ระบุในพารามิเตอร์การค้นหา login_hint
  • email: อีเมลของผู้ใช้

สร้างไฟล์ schema.sql ในไดเรกทอรี resources ของโมดูล 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 ที่บันทึกไว้ในฐานข้อมูลได้ เพิ่มคำอธิบายประกอบ @Table ด้วยชื่อตารางที่เกี่ยวข้องซึ่งคุณกำหนดค่าไว้ใน schema.sql

โปรดทราบว่าตัวอย่างโค้ดจะมีตัวสร้างและตัวตั้งค่าสำหรับแอตทริบิวต์ทั้ง 2 รายการ มีการใช้ตัวสร้างและตัวตั้งค่าใน AuthController.java เพื่อสร้างหรืออัปเดตผู้ใช้ในฐานข้อมูล คุณอาจรวม Getters และเมธอด 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;
}

ตั้งค่าฐานข้อมูล

หากต้องการจัดเก็บข้อมูลที่เกี่ยวข้องกับผู้ใช้ ให้ใช้ฐานข้อมูล H2 ที่รองรับภายใน Spring Boot ฐานข้อมูลนี้ยังใช้ในคำแนะนำแบบทีละขั้นเพื่อจัดเก็บข้อมูลอื่นๆ ที่เกี่ยวข้องกับ Classroom ด้วย การตั้งค่าฐานข้อมูล 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 จะสร้างไดเรกทอรีที่ชื่อ h2 โดยมีไฟล์ userdb จัดเก็บไว้ภายใน เพิ่มเส้นทางไปยังฐานข้อมูล H2 ไปยัง .gitignore คุณต้องอัปเดต spring.datasource.username และ spring.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;
}

อัปเดตเมธอด getFlow() ใน AuthService.java เพื่อรวม setDataStoreFactory ในเมธอด GoogleAuthorizationCodeFlow Builder() และเรียก getCredentialDataStore() เพื่อตั้งค่าพื้นที่เก็บข้อมูล

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

จากนั้นอัปเดตเมธอด getAndSaveCredentials(String authorizationCode) ก่อนหน้านี้ วิธีการนี้จะได้รับข้อมูลเข้าสู่ระบบโดยไม่มีการจัดเก็บไว้ที่ใดเลย อัปเดตเมธอดเพื่อจัดเก็บข้อมูลเข้าสู่ระบบในพื้นที่เก็บข้อมูลที่จัดทำดัชนีตามรหัสผู้ใช้

รับ User-ID จากออบเจ็กต์ TokenResponse โดยใช้ id_token ได้ แต่ต้องได้รับการยืนยันก่อน มิฉะนั้น แอปพลิเคชันไคลเอ็นต์อาจแอบอ้างเป็นผู้ใช้ได้โดยการส่งรหัสผู้ใช้ที่แก้ไขแล้วไปยังเซิร์ฟเวอร์ เราขอแนะนำให้คุณใช้ไลบรารีของไคลเอ็นต์ Google API เพื่อตรวจสอบ id_token ดูข้อมูลเพิ่มเติมได้ที่ [หน้า Google Identity เกี่ยวกับการยืนยันโทเค็นรหัส Google]

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

อัปเดตการโทรเป็น flow.createAndStoreCredential เพื่อรวม userId

// 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 คุณจะได้รับ id ในพารามิเตอร์การค้นหา login_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 ให้กำหนดเมธอดเพื่อเรียกข้อมูลผู้ใช้จากฐานข้อมูลตามรหัสผู้ใช้

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

ข้อมูลเข้าสู่ระบบของร้านค้า

คุณสามารถจัดเก็บข้อมูลเข้าสู่ระบบได้ 2 กรณี หาก 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()

มี 2 อินสแตนซ์ที่คุณอาจบันทึกข้อมูลเข้าสู่ระบบไว้ในฐานข้อมูล ได้แก่ เมื่อผู้ใช้กลับไปที่แอปพลิเคชันของคุณเมื่อสิ้นสุดขั้นตอนการให้สิทธิ์ และเมื่อออกการเรียก API ตรงนี้คือตำแหน่งที่เราตั้งค่าคีย์ credentials ของเซสชันก่อนหน้านี้

โทรหา save_user_credentials เมื่อสุดเส้นทางของ callback เก็บออบเจ็กต์ 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

ก่อนอื่นให้กำหนดเมธอดที่จัดเก็บหรืออัปเดตออบเจ็กต์ User ในฐานข้อมูล 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);
    }
}

มี 2 อินสแตนซ์ที่คุณอาจบันทึกข้อมูลเข้าสู่ระบบไว้ในฐานข้อมูล ได้แก่ เมื่อผู้ใช้กลับไปที่แอปพลิเคชันของคุณเมื่อสิ้นสุดขั้นตอนการให้สิทธิ์ และเมื่อออกการเรียก API ตรงนี้คือตำแหน่งที่เราตั้งค่าคีย์ credentials ของเซสชันก่อนหน้านี้

โทรหา saveUser เมื่อสุดเส้นทางของ /callback คุณควรเก็บออบเจ็กต์ 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 ที่บังคับใช้นโยบายการควบคุมเซสชัน

รับโทเค็นใหม่โดยการส่งผู้ใช้ผ่านขั้นตอนการให้สิทธิ์อีกครั้งหากข้อมูลเข้าสู่ระบบของผู้ใช้ไม่ถูกต้อง

กำหนดเส้นทางผู้ใช้โดยอัตโนมัติ

แก้ไขเส้นทาง Landing Page ของส่วนเสริมเพื่อตรวจหาว่าผู้ใช้ได้ให้สิทธิ์แอปพลิเคชันของเราก่อนหน้านี้หรือไม่ หากใช่ โปรดนำผู้ใช้ไปยังหน้าส่วนเสริมหลักของเรา หรือแจ้งให้ ผู้ใช้ลงชื่อเข้าใช้

Python

ตรวจสอบว่าสร้างไฟล์ฐานข้อมูลแล้วเมื่อแอปพลิเคชันเปิด แทรกรายการต่อไปนี้ลงในเครื่องมือเริ่มต้นโมดูล (เช่น webapp/__init__.py ในตัวอย่างที่เราให้ไว้) หรือในเมธอดหลักที่เปิดเซิร์ฟเวอร์

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

จากนั้นวิธีการของคุณควรจัดการพารามิเตอร์การค้นหาของ login_hint ตามที่ที่กล่าวถึงข้างต้น จากนั้นโหลดข้อมูลเข้าสู่ระบบ Store หากเป็นผู้เข้าชมซ้ำ คุณทราบว่าเป็นผู้เข้าชมซ้ำหากได้รับ 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

ไปที่เส้นทาง Landing Page ของส่วนเสริม (/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 ให้โหลดข้อมูลเข้าสู่ระบบจากที่เก็บข้อมูลเข้าสู่ระบบที่ตั้งค่าไว้ก่อนหน้านี้ แล้วตั้งค่าข้อมูลเข้าสู่ระบบในเซสชัน หากข้อมูลเข้าสู่ระบบไม่ได้รับมาจากพื้นที่เก็บข้อมูลเข้าสู่ระบบ ให้กำหนดเส้นทางผู้ใช้ผ่านขั้นตอนการตรวจสอบสิทธิ์ด้วยการเรียกใช้ 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);
    }
}

สุดท้าย ให้เปลี่ยนเส้นทางผู้ใช้ไปยังหน้า Landing Page ของส่วนเสริม

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

ทดสอบส่วนเสริม

ลงชื่อเข้าใช้ Google Classroom ในฐานะผู้ใช้การทดสอบของ ครู ไปที่แท็บงานของชั้นเรียนและสร้างงานใหม่ คลิกปุ่มส่วนเสริมใต้พื้นที่ข้อความ จากนั้นเลือกส่วนเสริม iframe จะเปิดขึ้นและส่วนเสริมจะโหลด URI การตั้งค่าไฟล์แนบที่คุณระบุไว้ในหน้าการกำหนดค่าแอปของ Google Workspace Marketplace SDK

ยินดีด้วย คุณก็พร้อมที่จะทำขั้นตอนถัดไปแล้ว นั่นคือการสร้างไฟล์แนบและระบุบทบาทของผู้ใช้