التعامل مع عمليات تسجيل الدخول المتكرّرة

هذه هي الجولةالثالثة في سلسلة جولاتنا التفصيلية حول إضافات Classroom.

في هذه الجولة الإرشادية، يمكنك معالجة الزيارات المتكررة إلى إضافتنا من خلال استرداد بيانات اعتماد المستخدم التي منحها سابقًا تلقائيًا. يمكنك بعد ذلك توجيه المستخدمين إلى صفحات يمكنهم من خلالها إصدار طلبات واجهة برمجة التطبيقات على الفور. هذا سلوك مطلوب لإضافات Classroom.

خلال هذه الجولة الإرشادية، يمكنك إكمال ما يلي:

  • تنفيذ سعة تخزين دائمة لبيانات اعتماد المستخدمين
  • استرداد مَعلمة طلب البحث الإضافية login_hint وتقييمها هذا هو رقم تعريف Google الفريد للمستخدم الذي سجّل الدخول.

بعد الانتهاء، يمكنك تفويض المستخدمين بالكامل في تطبيق الويب الخاص بك وإصدار طلبات إلى Google APIs.

فهم مَعلمات طلب البحث في إطار iframe

تحمِّل Classroom عنوان URL لإعداد المرفق الخاص بإضافتك عند فتحها. يلصِق 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 للحصول على وصف detailed لهذا السيناريو.

هناك خمس مَعلمات طلب بحث محتمَلة لعنوان URL الخاص بمستند الاستكشاف:

  • courseId: رقم تعريف الدورة التدريبية الحالية في Classroom
  • itemId: رقم تعريف عنصر البث الذي يعدّله المستخدم أو ينشئه.
  • itemType: نوع عنصر البث الذي ينشئه المستخدم أو يعدّله، وهو أحد الأنواع التالية: courseWork أو courseWorkMaterial أو announcement.
  • addOnToken: رمز مميّز يُستخدَم للسماح ببعض إجراءات إضافة Classroom.
  • login_hint: رقم تعريف المستخدم الحالي على Google

يتناول هذا الدليل التفصيلي login_hint. يتم توجيه المستخدمين استنادًا إلى ما إذا كانت مَعلمة طلب البحث هذه متوفرة، إما إلى عملية التفويض في حال عدم توفّرها، أو إلى صفحة اكتشاف الإضافة في حال توفّرها.

الوصول إلى مَعلمات طلب البحث

يتمّ تمرير مَعلمات طلب البحث إلى تطبيق الويب في سلسلة معرّف الموارد المنتظم (URI). تخزِّن هذه القيم في جلستك، ويتم استخدامها في عملية التفويض و لتخزين معلومات عن المستخدم واستردادها. ولا يتم تمرير مَعلمات طلبات البحث هذه إلا عند فتح الإضافة لأول مرة.

Python

انتقِل إلى تعريفات مسارات Flask (routes.py إذا كنت تتبع المثال المقدَّم). في أعلى مسار الإحالة الناجحة الإضافية (/classroom-addon في المثال المقدَّم)، استرجع مَعلمة طلب البحث login_hint واحفظها:

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

تأكَّد من تخزين login_hint (إن توفّر) في الجلسة. هذا هو الموقع المناسب لتخزين هذه القيم، فهي مؤقتة وستتلقّى قيمًا جديدة عند فتح الإضافة.

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

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

Java

انتقِل إلى مسار الصفحة المقصودة للإضافات في فئة وحدة التحكّم (/addon-discovery في AuthController.java في المثال المقدَّم). في بداية هذا المسار، استرجع مَعلمةlogin_hint query واحفظها.

/** 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 إلى خوادم مصادقة 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 كمَعلمة إلى الطريقة، وأضِفlogin_hint والوسيطة إلى أداة إنشاء عناوين URL لمنح الأذونات.

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

إضافة مساحة تخزين دائمة لبيانات اعتماد المستخدم

إذا تلقّيت القيمة login_hint كمَعلمة طلب بحث عند تحميل الإضافة، يعني ذلك أنّه يشير إلى أنّ المستخدم قد أكمل عملية التفويض لتطبيقنا. يجب استرداد بيانات الاعتماد السابقة بدلاً من إجبارهم على تسجيل الدخول مرة أخرى.

تذكَّر أنّك تلقّيت رمز إعادة التنشيط عند اكتمال خطوات منح الإذن. احفظ هذا الرمز المميّز، وسيتم إعادة استخدامه للحصول على رمز دخول، وهو رمز مؤقت وضروري لاستخدام Google APIs. سبق لك حفظ هذه بيانات الاعتماد في الجلسة، ولكن عليك تخزين بيانات الاعتماد لمعالجة الزيارات المتكرّرة.

تحديد مخطّط المستخدم وإعداد قاعدة البيانات

إعداد مخطّط قاعدة بيانات 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.

يُرجى العلم أنّ مثال الرمز البرمجي يتضمّن وظائف إنشاء وإعداد لسمتَي. يتم استخدام أسلوب الإنشاء وطرق الضبط في 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() لضبط قاعدة البيانات.

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

بعد ذلك، عدِّل طريقة getAndSaveCredentials(String authorizationCode). في السابق، كانت هذه الطريقة تحصل على بيانات الاعتماد بدون تخزينها في أي مكان. عدِّل طريقة تخزين بيانات الاعتماد في قاعدة البيانات المُفهرسة حسب رقم تعريف المستخدم.

يمكن الحصول على رقم تعريف المستخدم من عنصر TokenResponse باستخدام id_token، ولكن يجب إثبات ملكيته أولاً. بخلاف ذلك، قد تتمكّن تطبيقات العميل من انتحال هوية المستخدمين من خلال إرسال أرقام تعريف مستخدمين معدَّلة إلى الخادم. ننصحك باستخدام مكتبات العميل في Google API للتحقّق من صحة id_token. اطّلِع على [صفحة "هوية Google" حول إثبات ملكية رمز تعريف 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;
}

تخزين بيانات الاعتماد

هناك سيناريوهان عند تخزين بيانات الاعتماد. إذا كان 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()

هناك حالتان قد تحفظ فيهما بيانات الاعتماد في قاعدة بياناتك: عندما يعود المستخدم إلى تطبيقك في نهاية تدفق التفويض وعند إصدار طلب بيانات من واجهة برمجة التطبيقات. هذه هي الأماكن التي سبق أن أعددنا فيها مفتاح الجلسة 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)

يجب أيضًا تعديل بيانات الاعتماد بعد إجراء طلبات البيانات من واجهة برمجة التطبيقات. في هذا الحالة، يمكنك تقديم بيانات الاعتماد المعدَّلة كوسيطات لطريقة 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);
    }
}

هناك حالتان قد تحفظ فيهما بيانات الاعتماد في قاعدة بياناتك: عندما يعود المستخدم إلى تطبيقك في نهاية تدفق التفويض وعند إصدار طلب بيانات من واجهة برمجة التطبيقات. هذه هي الأماكن التي سبق أن أعددنا فيها مفتاح الجلسة 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);

يجب أيضًا تعديل بيانات الاعتماد بعد إجراء طلبات البيانات من واجهة برمجة التطبيقات. في هذا السياق، يمكنك تقديم بيانات الاعتماد المعدَّلة كوسيطات لطريقة saveUser.

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

بيانات اعتماد منتهية الصلاحية

يُرجى العِلم أنّ هناك بعض الأسباب التي قد تؤدي إلى أن تصبح رموز إعادة التنشيط غير صالحة. وتشمل هذه العناصر ما يلي:

  • لم يتم استخدام الرمز المميّز لإعادة التحميل منذ ستة أشهر.
  • إذا ألغى المستخدم أذونات الوصول إلى تطبيقك
  • يغيّر المستخدم كلمات المرور.
  • ينتمي المستخدم إلى مؤسسة على Google Cloud لديها سياسات التحكّم في الجلسات فعالة.

يمكنك الحصول على رموز مميّزة جديدة من خلال توجيه المستخدم إلى عملية التفويض مرة أخرى إذا أصبحت بيانات اعتماده غير صالحة.

توجيه المستخدم تلقائيًا

تعديل مسار الصفحة المقصودة للإضافات لرصد ما إذا كان المستخدم قد فوّض في السابق تطبيقنا إذا كان الأمر كذلك، يمكنك توجيههم إلى صفحة الإضافة الرئيسية. وبخلاف ذلك، اطلب منه تسجيل الدخول.

Python

تأكَّد من أنّه تم إنشاء ملف قاعدة البيانات عند بدء تشغيل التطبيق. أدخِل ما يلي في أداة إعداد الوحدة (مثل webapp/__init__.py في المثال المقدَّم) أو في الطريقة الرئيسية التي تشغِّل الخادم.

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

من المفترض أن تعالج طريقتك مَعلمة طلب البحث login_hint على النحو الذيناقشناه أعلاه. بعد ذلك، حمِّل بيانات اعتماد المتجر إذا كان هذا ملفًا شخصيًا لمستخدم يزور المتجر بشكل متكرر. يمكنك معرفة أنّه زائر متكرّر إذا تلقّيت login_hint. استرداد أي بيانات اعتماد مخزّنة لهذا المستخدم وتحميلها إلى الجلسة

stored_credentials = get_credentials_from_storage(login_hint)

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

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

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

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

أخيرًا، يمكنك توجيه المستخدم إلى صفحة تسجيل الدخول إذا لم تكن لدينا بيانات اعتماده. وفي هذه الحالة، سيتم توجيههم إلى صفحة الإضافة الرئيسية.

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

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

Java

انتقِل إلى مسار الصفحة المقصودة للإضافات (/addon-discovery في المثال الذي تم تقديمه). كما أوضحنا أعلاه، هذا هو المكان الذي تعاملت فيه مع مَعلمة طلب البحث login_hint.

أولاً، تحقّق مما إذا كانت بيانات الاعتماد متوفّرة في الجلسة. وإذا لم يكن الأمر كذلك، يمكنك توجيه العميل من خلال عملية المصادقة من خلال استدعاء الطريقة startAuthFlow.

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

بعد ذلك، حمِّل المستخدم من قاعدة بيانات H2 إذا كان هذا زائرًا متكررًا. يكون الزائر مكرّرًا إذا تلقّيت مَعلمة طلب البحث login_hint. إذا كان المستخدم متوفّرًا في قاعدة بيانات H2، حمِّل بيانات الاعتماد من مستودع بيانات بيانات الاعتماد الذي تم إعداده سابقًا، واضبط بيانات الاعتماد في الجلسة. إذا لم يتم الحصول على بيانات الاعتماد من قاعدة بيانات بيانات الاعتماد، يمكنك توجيه المستخدم من خلال عملية المصادقة من خلال الاتصال بـ 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 بصفتك أحد المعلّمين المشارِكين في الاختبار. انتقِل إلى علامة التبويب الواجب الدراسي وأنشِئ واجبًا دراسيًا جديدًا. انقر على زر الإضافات أسفل منطقة النص، ثم اختَر الإضافة. يتم فتح الإطار المتداخل ويحمِّل التطبيق الإضافة عنوان URL لإعداد المرفق الذي حدّدته في صفحة إعداد التطبيق في حزمة تطوير البرامج (SDK) في Google Workspace Marketplace.

تهانينا! أنت الآن جاهز للانتقال إلى الخطوة التالية: إنشاء مرفقات وتحديد دور المستخدم.