ورودهای تکراری را مدیریت کنید

این سومین راهنما در مجموعه مروری بر افزونه‌های Classroom است.

در این راهنما، بازدیدهای مکرر از افزونه ما را با بازیابی خودکار اعتبارنامه‌های قبلاً اعطا شده کاربر انجام می‌دهید. سپس کاربران را به صفحاتی هدایت می‌کنید که از آن‌ها می‌توانند بلافاصله درخواست‌های API را صادر کنند. این یک رفتار ضروری برای افزونه‌های Classroom است.

در طول این راهنما، موارد زیر را تکمیل می کنید:

  • فضای ذخیره سازی دائمی را برای اعتبار کاربری ما پیاده سازی کنید.
  • پارامتر پرس و جوی افزودنی login_hint را بازیابی و ارزیابی کنید. این یک شماره شناسه Google منحصر به فرد کاربر وارد شده به سیستم است.

پس از اتمام، می توانید به طور کامل به کاربران در برنامه وب خود مجوز دهید و با Google API تماس بگیرید.

پارامترهای پرس و جوی 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 شناسه های رشته هستند. برای توضیح دقیق این سناریو به راهنمای iframes مراجعه کنید.

پنج پارامتر پرس و جو ممکن برای URL کشف وجود دارد:

  • courseId : شناسه دوره فعلی Classroom.
  • itemId : شناسه مورد جریانی که کاربر در حال ویرایش یا ایجاد آن است.
  • itemType : نوع مورد جریانی که کاربر در حال ایجاد یا ویرایش است، البته یک courseWork ، courseWorkMaterial یا announcement .
  • addOnToken : رمزی که برای مجوز دادن به برخی از اقدامات الحاقی Classroom استفاده می شود.
  • login_hint : شناسه Google کاربر فعلی.

این راهنما آدرس login_hint را نشان می دهد. کاربران بر اساس اینکه آیا این پارامتر پرس و جو ارائه شده است، یا به جریان مجوز در صورت عدم وجود، یا به صفحه کشف افزودنی در صورت وجود، مسیریابی می شوند.

دسترسی به پارامترهای پرس و جو

پارامترهای پرس و جو در رشته URI به برنامه وب شما ارسال می شوند. این مقادیر را در جلسه خود ذخیره کنید. آنها در جریان مجوز و برای ذخیره و بازیابی اطلاعات کاربر استفاده می شوند. این پارامترهای پرس و جو فقط زمانی ارسال می شوند که افزونه برای اولین بار باز شود.

پایتون

به تعاریف مسیرهای Flask خود بروید (اگر از مثال ارائه شده ما پیروی می کنید routes.py ). در بالای مسیر فرود افزونه خود ( /classroom-addon در مثال ارائه شده ما)، پارامتر query 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()

جاوا

به مسیر فرود افزودنی در کلاس کنترلر خود بروید ( /addon-discovery در AuthController.java در مثال ارائه شده). در ابتدای این مسیر، پارامتر query login_hint را بازیابی و ذخیره کنید.

/** Retrieve the login_hint query parameter from the request URL if present. */
String login_hint = request.getParameter("login_hint");

اطمینان حاصل کنید که login_hint (در صورت وجود) در جلسه ذخیره شده است. این مکان مناسبی برای ذخیره این مقادیر است. آنها زودگذر هستند و با باز شدن افزونه مقادیر جدیدی دریافت می کنید.

/** If login_hint wasn't sent, use the values in the session. */
if (login_hint == null) {
    login_hint = (String) session.getAttribute("login_hint");
}

/** If the there is still no login_hint, route the user to the authorization
 *  page. */
if (login_hint == null) {
    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 باید به سرورهای احراز هویت گوگل نیز ارسال شود. این فرآیند احراز هویت را تسهیل می کند. اگر برنامه شما بداند که کدام کاربر در حال تلاش برای احراز هویت است، سرور از راهنمایی برای ساده کردن جریان ورود با پر کردن فیلد ایمیل در فرم ورود استفاده می کند.

پایتون

به مسیر مجوز در فایل سرور 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"),

جاوا

به متد authorize() در کلاس AuthService.java بروید. login_hint به عنوان پارامتر به متد اضافه کنید و login_hint و آرگومان را به سازنده URL مجوز اضافه کنید.

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

فضای ذخیره‌سازی دائمی برای اطلاعات کاربری کاربر اضافه کنید

اگر هنگام بارگیری افزونه، login_hint به‌عنوان پارامتر پرس و جو دریافت می‌کنید، نشان‌دهنده این است که کاربر قبلاً جریان مجوز را برای برنامه ما تکمیل کرده است. به جای اینکه آنها را مجبور کنید دوباره وارد سیستم شوند، باید اعتبار قبلی آنها را بازیابی کنید.

به یاد داشته باشید که پس از تکمیل جریان مجوز، یک نشانه به‌روزرسانی دریافت کرده‌اید. این نشانه را ذخیره کنید. از آن برای به دست آوردن رمز دسترسی استفاده مجدد شود، که عمر کوتاهی دارد و برای استفاده از APIهای Google ضروری است. قبلاً این اعتبارنامه‌ها را در جلسه ذخیره کرده‌اید، اما برای مدیریت بازدیدهای مکرر باید اعتبارنامه‌ها را ذخیره کنید.

طرح کاربر را تعریف کرده و پایگاه داده را راه اندازی کنید

یک طرح واره پایگاه داده برای یک User تنظیم کنید.

پایتون

طرح کاربر را تعریف کنید

یک User دارای ویژگی های زیر است:

  • id : شناسه گوگل کاربر. این باید با مقادیر ارائه شده در پارامتر query login_hint مطابقت داشته باشد.
  • display_name : نام و نام خانوادگی کاربر، مانند "Alex Smith".
  • email : آدرس ایمیل کاربر.
  • portrait_url : URL عکس نمایه کاربر.
  • refresh_token : توکن refresh که قبلا به دست آمده است.

این مثال با استفاده از 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

این فلاسک را به فایل 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()

جاوا

طرح کاربر را تعریف کنید

یک User دارای ویژگی های زیر است:

  • id : شناسه گوگل کاربر. این باید با مقدار ارائه شده در پارامتر query 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
);

یک کلاس جاوا برای تعریف مدل User برای پایگاه داده ایجاد کنید. این User.java در مثال ارائه شده است.

حاشیه نویسی @Entity را اضافه کنید تا نشان دهید که این یک POJO است که می تواند در پایگاه داده ذخیره شود. حاشیه‌نویسی @Table را با نام جدول مربوطه که در schema.sql پیکربندی کرده‌اید، اضافه کنید.

توجه داشته باشید که مثال کد شامل سازنده و تنظیم کننده برای دو ویژگی است. سازنده و تنظیم کننده ها در AuthController.java برای ایجاد یا به روز رسانی یک کاربر در پایگاه داده استفاده می شوند. همچنین می‌توانید دریافت‌کننده‌ها و متد 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() را برای تنظیم datastore فراخوانی کنید.

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

در مرحله بعد، متد getAndSaveCredentials(String authorizationCode) را به روز کنید. پیش از این، این روش بدون اینکه در جایی ذخیره شود، اعتبار به دست می آورد. روش ذخیره اعتبارنامه ها را در datastore نمایه شده توسط شناسه کاربر به روز کنید.

شناسه کاربر را می توان از شی TokenResponse با استفاده از id_token دریافت کرد، اما ابتدا باید تأیید شود. در غیر این صورت، برنامه های سرویس گیرنده ممکن است بتوانند با ارسال شناسه های کاربری اصلاح شده به سرور، هویت کاربران را جعل کنند. توصیه می شود از کتابخانه های Google API Client برای تأیید اعتبار id_token استفاده کنید. برای اطلاعات بیشتر به [صفحه Google Identity در تأیید کد 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();

تماس را به flow.createAndStoreCredential به روز کنید تا userId را نیز در بر گیرد.

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

متدی را به کلاس AuthService.java اضافه کنید که در صورت وجود در 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;
    }
}

بازیابی اعتبار

روشی را برای واکشی Users تعریف کنید. شما یک id در پارامتر query login_hint ارائه کرده‌اید که می‌توانید از آن برای بازیابی یک رکورد کاربر خاص استفاده کنید.

پایتون

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

جاوا

در کلاس 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;
}

اعتبار فروشگاه

هنگام ذخیره اعتبار دو سناریو وجود دارد. اگر id کاربر از قبل در پایگاه داده است، رکورد موجود را با مقادیر جدید به روز کنید. در غیر این صورت، یک رکورد User جدید ایجاد کنید و آن را به پایگاه داده اضافه کنید.

پایتون

ابتدا یک روش کاربردی که رفتار ذخیره سازی یا به روز رسانی را پیاده سازی می کند، تعریف کنید.

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 جلسه را تنظیم کرده بودیم.

با 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)

جاوا

ابتدا روشی را تعریف کنید که یک شی 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);
    }
}

دو مورد وجود دارد که ممکن است اعتبارنامه ها را در پایگاه داده خود ذخیره کنید: زمانی که کاربر در پایان جریان مجوز به برنامه شما باز می گردد و هنگام صدور یک تماس 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);

اعتبار منقضی شده

توجه داشته باشید که چند دلیل وجود دارد که نشانه‌های تازه‌سازی ممکن است نامعتبر شوند. این موارد عبارتند از:

  • توکن رفرش شش ماه است که استفاده نشده است.
  • کاربر مجوزهای دسترسی برنامه شما را لغو می کند.
  • کاربر رمز عبور را تغییر می دهد.
  • کاربر متعلق به یک سازمان Google Cloud است که خط‌مشی‌های کنترل جلسه را در اختیار دارد.

در صورتی که اعتبار کاربر نامعتبر شد، با ارسال مجدد کاربر از طریق جریان مجوز، توکن‌های جدیدی به دست آورید.

مسیریابی خودکار کاربر

مسیر فرود افزودنی را تغییر دهید تا تشخیص دهید آیا کاربر قبلاً برنامه ما را مجاز کرده است یا خیر. اگر چنین است، آنها را به صفحه افزودنی اصلی ما هدایت کنید. در غیر این صورت، از آنها بخواهید وارد سیستم شوند.

پایتون

اطمینان حاصل کنید که فایل پایگاه داده هنگام راه اندازی برنامه ایجاد شده است. موارد زیر را در یک ماژول اولیه (مانند webapp/__init__.py در مثال ارائه شده ما) یا در روش اصلی که سرور را راه اندازی می کند، وارد کنید.

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

سپس روش شما باید پارامتر query 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.")

جاوا

به مسیر فرود افزونه خود بروید ( /addon-discovery در مثال ارائه شده). همانطور که در بالا توضیح داده شد ، اینجا جایی است که پارامتر query 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);
    }
}

در نهایت کاربر را به صفحه فرود افزونه هدایت کنید.

/** 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 وارد شوید. به برگه Classwork بروید و یک تکلیف جدید ایجاد کنید. روی دکمه Add-ons در زیر ناحیه متن کلیک کنید، سپس افزونه خود را انتخاب کنید. iframe باز می‌شود و افزونه URI تنظیم پیوست را که در صفحه پیکربندی برنامه Google Workspace Marketplace SDK مشخص کرده‌اید، بارگیری می‌کند.

تبریک می گویم! شما آماده‌اید تا به مرحله بعدی بروید: ایجاد پیوست‌ها و شناسایی نقش کاربر .

،

این سومین راهنما در مجموعه مروری بر افزونه‌های Classroom است.

در این راهنما، بازدیدهای مکرر از افزونه ما را با بازیابی خودکار اعتبارنامه‌های قبلاً اعطا شده کاربر انجام می‌دهید. سپس کاربران را به صفحاتی هدایت می‌کنید که از آن‌ها می‌توانند بلافاصله درخواست‌های API را صادر کنند. این یک رفتار ضروری برای افزونه‌های Classroom است.

در طول این راهنما، موارد زیر را تکمیل می کنید:

  • فضای ذخیره سازی دائمی را برای اعتبار کاربری ما پیاده سازی کنید.
  • پارامتر پرس و جوی افزودنی login_hint را بازیابی و ارزیابی کنید. این یک شماره شناسه Google منحصر به فرد کاربر وارد شده به سیستم است.

پس از اتمام، می توانید به طور کامل به کاربران در برنامه وب خود مجوز دهید و با Google API تماس بگیرید.

پارامترهای پرس و جوی 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 شناسه های رشته هستند. برای توضیح دقیق این سناریو به راهنمای iframes مراجعه کنید.

پنج پارامتر پرس و جو ممکن برای URL کشف وجود دارد:

  • courseId : شناسه دوره فعلی Classroom.
  • itemId : شناسه مورد جریانی که کاربر در حال ویرایش یا ایجاد آن است.
  • itemType : نوع مورد جریانی که کاربر در حال ایجاد یا ویرایش است، البته یک courseWork ، courseWorkMaterial یا announcement .
  • addOnToken : رمزی که برای مجوز دادن به برخی از اقدامات الحاقی Classroom استفاده می شود.
  • login_hint : شناسه Google کاربر فعلی.

این راهنما آدرس login_hint را نشان می دهد. کاربران بر اساس اینکه آیا این پارامتر پرس و جو ارائه شده است، یا به جریان مجوز در صورت عدم وجود، یا به صفحه کشف افزودنی در صورت وجود، مسیریابی می شوند.

دسترسی به پارامترهای پرس و جو

پارامترهای پرس و جو در رشته URI به برنامه وب شما ارسال می شوند. این مقادیر را در جلسه خود ذخیره کنید. آنها در جریان مجوز و برای ذخیره و بازیابی اطلاعات کاربر استفاده می شوند. این پارامترهای پرس و جو فقط زمانی ارسال می شوند که افزونه برای اولین بار باز شود.

پایتون

به تعاریف مسیرهای Flask خود بروید (اگر از مثال ارائه شده ما پیروی می کنید routes.py ). در بالای مسیر فرود افزونه خود ( /classroom-addon در مثال ارائه شده ما)، پارامتر query 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()

جاوا

به مسیر فرود افزودنی در کلاس کنترلر خود بروید ( /addon-discovery در AuthController.java در مثال ارائه شده). در ابتدای این مسیر، پارامتر query login_hint را بازیابی و ذخیره کنید.

/** Retrieve the login_hint query parameter from the request URL if present. */
String login_hint = request.getParameter("login_hint");

اطمینان حاصل کنید که login_hint (در صورت وجود) در جلسه ذخیره شده است. این مکان مناسبی برای ذخیره این مقادیر است. آنها زودگذر هستند و با باز شدن افزونه مقادیر جدیدی دریافت می کنید.

/** If login_hint wasn't sent, use the values in the session. */
if (login_hint == null) {
    login_hint = (String) session.getAttribute("login_hint");
}

/** If the there is still no login_hint, route the user to the authorization
 *  page. */
if (login_hint == null) {
    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 باید به سرورهای احراز هویت گوگل نیز ارسال شود. این فرآیند احراز هویت را تسهیل می کند. اگر برنامه شما بداند که کدام کاربر در حال تلاش برای احراز هویت است، سرور از راهنمایی برای ساده کردن جریان ورود با پر کردن فیلد ایمیل در فرم ورود استفاده می کند.

پایتون

به مسیر مجوز در فایل سرور 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"),

جاوا

به متد authorize() در کلاس AuthService.java بروید. login_hint به عنوان پارامتر به متد اضافه کنید و login_hint و آرگومان را به سازنده URL مجوز اضافه کنید.

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

فضای ذخیره‌سازی دائمی برای اطلاعات کاربری کاربر اضافه کنید

اگر هنگام بارگیری افزونه، login_hint به‌عنوان پارامتر پرس و جو دریافت می‌کنید، نشان‌دهنده این است که کاربر قبلاً جریان مجوز را برای برنامه ما تکمیل کرده است. به جای اینکه آنها را مجبور کنید دوباره وارد سیستم شوند، باید اعتبار قبلی آنها را بازیابی کنید.

به یاد داشته باشید که پس از تکمیل جریان مجوز، یک نشانه به‌روزرسانی دریافت کرده‌اید. این نشانه را ذخیره کنید. از آن برای به دست آوردن رمز دسترسی استفاده مجدد شود، که عمر کوتاهی دارد و برای استفاده از APIهای Google ضروری است. قبلاً این اعتبارنامه‌ها را در جلسه ذخیره کرده‌اید، اما برای مدیریت بازدیدهای مکرر باید اعتبارنامه‌ها را ذخیره کنید.

طرح کاربر را تعریف کرده و پایگاه داده را راه اندازی کنید

یک طرح واره پایگاه داده برای یک User تنظیم کنید.

پایتون

طرح کاربر را تعریف کنید

یک User دارای ویژگی های زیر است:

  • id : شناسه گوگل کاربر. این باید با مقادیر ارائه شده در پارامتر query login_hint مطابقت داشته باشد.
  • display_name : نام و نام خانوادگی کاربر، مانند "Alex Smith".
  • email : آدرس ایمیل کاربر.
  • portrait_url : URL عکس نمایه کاربر.
  • refresh_token : توکن refresh که قبلا به دست آمده است.

این مثال با استفاده از 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

این فلاسک را به فایل 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()

جاوا

طرح کاربر را تعریف کنید

یک User دارای ویژگی های زیر است:

  • id : شناسه گوگل کاربر. این باید با مقدار ارائه شده در پارامتر query 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
);

یک کلاس جاوا برای تعریف مدل User برای پایگاه داده ایجاد کنید. این User.java در مثال ارائه شده است.

حاشیه نویسی @Entity را اضافه کنید تا نشان دهید که این یک POJO است که می تواند در پایگاه داده ذخیره شود. حاشیه‌نویسی @Table را با نام جدول مربوطه که در schema.sql پیکربندی کرده‌اید، اضافه کنید.

توجه داشته باشید که مثال کد شامل سازنده و تنظیم کننده برای دو ویژگی است. سازنده و تنظیم کننده ها در AuthController.java برای ایجاد یا به روز رسانی یک کاربر در پایگاه داده استفاده می شوند. همچنین می‌توانید دریافت‌کننده‌ها و متد 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() را برای تنظیم datastore فراخوانی کنید.

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

در مرحله بعد، متد getAndSaveCredentials(String authorizationCode) را به روز کنید. پیش از این، این روش بدون اینکه در جایی ذخیره شود، اعتبار به دست می آورد. روش ذخیره اعتبارنامه ها را در datastore نمایه شده توسط شناسه کاربر به روز کنید.

شناسه کاربر را می توان از شی TokenResponse با استفاده از id_token دریافت کرد، اما ابتدا باید تأیید شود. در غیر این صورت، برنامه های سرویس گیرنده ممکن است بتوانند با ارسال شناسه های کاربری اصلاح شده به سرور، هویت کاربران را جعل کنند. توصیه می شود از کتابخانه های Google API Client برای تأیید اعتبار id_token استفاده کنید. برای اطلاعات بیشتر به [صفحه Google Identity در تأیید کد 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();

تماس را به flow.createAndStoreCredential به روز کنید تا userId را نیز در بر گیرد.

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

متدی را به کلاس AuthService.java اضافه کنید که در صورت وجود در 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;
    }
}

بازیابی اعتبار

روشی را برای واکشی Users تعریف کنید. شما یک id در پارامتر query login_hint ارائه کرده‌اید که می‌توانید از آن برای بازیابی یک رکورد کاربر خاص استفاده کنید.

پایتون

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

جاوا

در کلاس 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;
}

اعتبار فروشگاه

هنگام ذخیره اعتبار دو سناریو وجود دارد. اگر id کاربر از قبل در پایگاه داده است، رکورد موجود را با مقادیر جدید به روز کنید. در غیر این صورت، یک رکورد User جدید ایجاد کنید و آن را به پایگاه داده اضافه کنید.

پایتون

ابتدا یک روش کاربردی که رفتار ذخیره سازی یا به روز رسانی را پیاده سازی می کند، تعریف کنید.

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 جلسه را تنظیم کرده بودیم.

با 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)

جاوا

ابتدا روشی را تعریف کنید که یک شی 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);
    }
}

دو مورد وجود دارد که ممکن است اعتبارنامه ها را در پایگاه داده خود ذخیره کنید: زمانی که کاربر در پایان جریان مجوز به برنامه شما باز می گردد و هنگام صدور یک تماس 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);

اعتبار منقضی شده

توجه داشته باشید که چند دلیل وجود دارد که توکن‌های تازه‌سازی ممکن است نامعتبر شوند. این موارد عبارتند از:

  • توکن رفرش شش ماه است که استفاده نشده است.
  • کاربر مجوزهای دسترسی برنامه شما را لغو می کند.
  • کاربر رمز عبور را تغییر می دهد.
  • کاربر متعلق به یک سازمان Google Cloud است که خط‌مشی‌های کنترل جلسه را در اختیار دارد.

در صورتی که اعتبار کاربر نامعتبر شد، با ارسال مجدد کاربر از طریق جریان مجوز، توکن‌های جدیدی به دست آورید.

مسیریابی خودکار کاربر

مسیر فرود افزودنی را تغییر دهید تا تشخیص دهید آیا کاربر قبلاً برنامه ما را مجوز داده است یا خیر. اگر چنین است، آنها را به صفحه اصلی افزونه ما هدایت کنید. در غیر این صورت، از آنها بخواهید وارد سیستم شوند.

پایتون

اطمینان حاصل کنید که فایل پایگاه داده هنگام راه اندازی برنامه ایجاد شده است. موارد زیر را در یک ماژول اولیه (مانند webapp/__init__.py در مثال ارائه شده ما) یا در روش اصلی که سرور را راه اندازی می کند، وارد کنید.

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

سپس روش شما باید پارامتر query 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.")

جاوا

به مسیر فرود افزونه خود بروید ( /addon-discovery در مثال ارائه شده). همانطور که در بالا توضیح داده شد ، اینجا جایی است که پارامتر query 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);
    }
}

در نهایت کاربر را به صفحه فرود افزونه هدایت کنید.

/** 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 وارد شوید. به برگه Classwork بروید و یک تکلیف جدید ایجاد کنید. روی دکمه Add-ons در زیر ناحیه متن کلیک کنید، سپس افزونه خود را انتخاب کنید. iframe باز می‌شود و افزونه URI تنظیم پیوست را که در صفحه پیکربندی برنامه Google Workspace Marketplace SDK مشخص کرده‌اید، بارگیری می‌کند.

تبریک می گویم! شما آماده‌اید تا به مرحله بعدی بروید: ایجاد پیوست‌ها و شناسایی نقش کاربر .