تسجيل دخول المستخدم

هذا هو الجزء الثاني من سلسلة الجولات الإرشادية حول إضافات Classroom.

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

خلال هذا الشرح، ستكمل ما يلي:

  • اضبط تطبيق الويب للحفاظ على بيانات الجلسة في إطار iframe.
  • تنفيذ مسار تسجيل الدخول من خادم إلى خادم باستخدام بروتوكول Google OAuth 2.0
  • إصدار طلب إلى واجهة برمجة التطبيقات OAuth 2.0
  • إنشاء مسارات إضافية لتوفير إمكانية التفويض وتسجيل الخروج واختبار طلبات البيانات من واجهة برمجة التطبيقات

بعد الانتهاء، يمكنك منح المستخدمين إذنًا كاملاً في تطبيقك على الويب وإجراء طلبات إلى واجهات Google API.

فهم عملية التفويض

تستخدم Google APIs بروتوكول OAuth 2.0 للمصادقة والتفويض. يتوفّر الوصف الكامل لتنفيذ بروتوكول OAuth من Google في دليل OAuth الخاص بخدمة "هوية Google".

تتم إدارة بيانات اعتماد تطبيقك في Google Cloud. بعد إنشاء هذه العناصر، عليك تنفيذ عملية من أربع خطوات لمصادقة المستخدم ومنحه الإذن:

  1. طلب الحصول على إذن قدِّم عنوان URL لردّ الاتصال كجزء من هذا الطلب. عند اكتمال العملية، ستتلقّى عنوان URL للتفويض.
  2. أعِد توجيه المستخدم إلى عنوان URL الخاص بالتفويض. تُعلم الصفحة الناتجة المستخدم بالأذونات التي يتطلّبها تطبيقك، وتطلب منه السماح بالوصول إليها. عند اكتمال العملية، تتم إعادة توجيه المستخدم إلى عنوان URL لمعاودة الاتصال.
  3. تلقّي رمز تفويض في مسار معاودة الاتصال استبدِل رمز التفويض برمز دخول ورمز إعادة تحميل.
  4. إجراء طلبات إلى إحدى واجهات Google API باستخدام الرموز المميزة

الحصول على بيانات اعتماد OAuth 2.0

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

تنفيذ عملية التفويض

أضِف منطقًا ومسارات إلى تطبيق الويب الخاص بنا لتحقيق التدفق الموصوف، بما في ذلك الميزات التالية:

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

بدء عملية التفويض

عدِّل صفحتك المقصودة لبدء عملية التفويض إذا لزم الأمر. يمكن أن تكون الإضافة في إحدى حالتين محتملتين: إما أن تكون هناك رموز مميزة محفوظة في الجلسة الحالية، أو أن تحتاج إلى الحصول على رموز مميزة من خادم OAuth 2.0. إجراء طلب اختبار من واجهة برمجة التطبيقات إذا كانت هناك رموز مميّزة في الجلسة، أو مطالبة المستخدم بتسجيل الدخول في حال عدم توفّرها

Python

افتح ملف routes.py. أولاً، اضبط بعض الثوابت وإعدادات ملف تعريف الارتباط وفقًا لاقتراحات أمان iframe.

# The file that contains the OAuth 2.0 client_id and client_secret.
CLIENT_SECRETS_FILE = "client_secret.json"

# The OAuth 2.0 access scopes to request.
# These scopes must match the scopes in your Google Cloud project's OAuth Consent
# Screen: https://console.cloud.google.com/apis/credentials/consent
SCOPES = [
    "openid",
    "https://www.googleapis.com/auth/userinfo.profile",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/classroom.addons.teacher",
    "https://www.googleapis.com/auth/classroom.addons.student"
]

# Flask cookie configurations.
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE="None",
)

انتقِل إلى مسار الصفحة المقصودة للإضافة (وهو /classroom-addon في ملف المثال). أضِف منطقًا لعرض صفحة تسجيل الدخول إذا كانت الجلسة لا تحتوي على مفتاح "بيانات الاعتماد".

@app.route("/classroom-addon")
def classroom_addon():
    if "credentials" not in flask.session:
        return flask.render_template("authorization.html")

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

Java

يمكن العثور على رمز هذا الشرح في الوحدة step_02_sign_in.

افتح الملف application.properties وأضِف إعدادات الجلسة التي تتوافق مع اقتراحات أمان إطار iframe.

# iFrame security recommendations call for cookies to have the HttpOnly and
# secure attribute set
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true

# Ensures that the session is maintained across the iframe and sign-in pop-up.
server.servlet.session.cookie.same-site=none

أنشئ فئة خدمة (AuthService.java في الوحدة step_02_sign_in) للتعامل مع منطق نقاط النهاية في ملف وحدة التحكّم، واضبط معرّف الموارد المنتظم (URI) لإعادة التوجيه وموقع ملف أسرار العميل والنطاقات التي تتطلّبها الإضافة. يُستخدَم معرّف الموارد المنتظم لإعادة التوجيه لإعادة توجيه المستخدمين إلى معرّف موارد منتظم محدّد بعد أن يمنحوا الإذن لتطبيقك. راجِع قسم "إعداد المشروع" في README.md في رمز المصدر للحصول على معلومات حول مكان وضع ملف client_secret.json.

@Service
public class AuthService {
    private static final String REDIRECT_URI = "https://localhost:5000/callback";
    private static final String CLIENT_SECRET_FILE = "client_secret.json";
    private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
    private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();

    private static final String[] REQUIRED_SCOPES = {
        "https://www.googleapis.com/auth/userinfo.profile",
        "https://www.googleapis.com/auth/userinfo.email",
        "https://www.googleapis.com/auth/classroom.addons.teacher",
        "https://www.googleapis.com/auth/classroom.addons.student"
    };

    /** Creates and returns a Collection object with all requested scopes.
    *   @return Collection of scopes requested by the application.
    */
    public static Collection<String> getScopes() {
        return new ArrayList<>(Arrays.asList(REQUIRED_SCOPES));
    }
}

افتح ملف وحدة التحكّم (AuthController.java في الوحدة step_02_sign_in) وأضِف منطقًا إلى مسار الصفحة المقصودة لعرض صفحة تسجيل الدخول إذا كانت الجلسة لا تحتوي على المفتاح credentials.

@GetMapping(value = {"/start-auth-flow"})
public String startAuthFlow(Model model) {
    try {
        return "authorization";
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

@GetMapping(value = {"/addon-discovery"})
public String addon_discovery(HttpSession session, Model model) {
    try {
        if (session == null || session.getAttribute("credentials") == null) {
            return startAuthFlow(model);
        }
        return "addon-discovery";
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

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

طلب الحصول على إذن

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

Python

أضِف عملية الاستيراد التالية إلى ملف routes.py.

import google_auth_oauthlib.flow

إنشاء مسار جديد /authorize أنشئ مثيلاً من google_auth_oauthlib.flow.Flow، وننصحك بشدة باستخدام طريقة from_client_secrets_file المضمّنة لإجراء ذلك.

@app.route("/authorize")
def authorize():
    # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow
    # steps.
    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        CLIENT_SECRETS_FILE, scopes=SCOPES)

اضبط flow's redirect_uri، وهو المسار الذي تريد أن يعود إليه المستخدمون بعد منح الإذن لتطبيقك. هذا هو /callback في المثال التالي.

# The URI created here must exactly match one of the authorized redirect
# URIs for the OAuth 2.0 client, which you configured in the API Console. If
# this value doesn't match an authorized URI, you will get a
# "redirect_uri_mismatch" error.
flow.redirect_uri = flask.url_for("callback", _external=True)

استخدِم عنصر التدفق لإنشاء authorization_url وstate. خزِّن state في الجلسة، إذ يُستخدَم للتحقّق من صحة استجابة الخادم لاحقًا. أخيرًا، أعِد توجيه المستخدم إلى 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")

# Store the state so the callback can verify the auth server response.
flask.session["state"] = state

# Redirect the user to the OAuth authorization URL.
return flask.redirect(authorization_url)

Java

أضِف الطرق التالية إلى ملف AuthService.java لإنشاء مثيل لكائن التدفق، ثم استخدِمه لاسترداد عنوان URL الخاص بالتفويض:

  • تقرأ الطريقة getClientSecrets() ملف سر العميل وتنشئ كائن GoogleClientSecrets.
  • تنشئ الدالة getFlow() مثيلاً من GoogleAuthorizationCodeFlow.
  • تستخدم الطريقة authorize() الكائن GoogleAuthorizationCodeFlow والمعلَمة state ومعرّف الموارد المنتظم لإعادة التوجيه من أجل استرداد عنوان URL الخاص بالتفويض. تُستخدَم المَعلمة state للتحقّق من صحة الردّ الوارد من خادم التفويض. بعد ذلك، تعرض الطريقة خريطة تتضمّن عنوان URL الخاص بالتفويض والمعلَمةstate.
/** Reads the client secret file downloaded from Google Cloud.
 *   @return GoogleClientSecrets read in from client secret file. */
public GoogleClientSecrets getClientSecrets() throws Exception {
    try {
        InputStream in = SignInApplication.class.getClassLoader()
            .getResourceAsStream(CLIENT_SECRET_FILE);
        if (in == null) {
            throw new FileNotFoundException("Client secret file not found: "
                +   CLIENT_SECRET_FILE);
        }
        GoogleClientSecrets clientSecrets = GoogleClientSecrets
            .load(JSON_FACTORY, new InputStreamReader(in));
        return clientSecrets;
    } catch (Exception e) {
        throw e;
    }
}

/** Builds and returns authorization code flow.
*   @return GoogleAuthorizationCodeFlow object used to retrieve an access
*   token and refresh token for the application.
*   @throws Exception if reading client secrets or building code flow object
*   is unsuccessful.
*/
public GoogleAuthorizationCodeFlow getFlow() throws Exception {
    try {
        GoogleAuthorizationCodeFlow authorizationCodeFlow =
            new GoogleAuthorizationCodeFlow.Builder(
                HTTP_TRANSPORT,
                JSON_FACTORY,
                getClientSecrets(),
                getScopes())
                .setAccessType("offline")
                .build();
        return authorizationCodeFlow;
    } catch (Exception e) {
        throw e;
    }
}

/** Builds and returns a map with the authorization URL, which allows the
*   user to give the app permission to their account, and the state parameter,
*   which is used to prevent cross site request forgery.
*   @return map with authorization URL and state parameter.
*   @throws Exception if building the authorization URL is unsuccessful.
*/
public HashMap authorize() throws Exception {
    HashMap<String, String> authDataMap = new HashMap<>();
    try {
        String state = new BigInteger(130, new SecureRandom()).toString(32);
        authDataMap.put("state", state);

        GoogleAuthorizationCodeFlow flow = getFlow();
        String authUrl = flow
            .newAuthorizationUrl()
            .setState(state)
            .setRedirectUri(REDIRECT_URI)
            .build();
        String url = authUrl;
        authDataMap.put("url", url);

        return authDataMap;
    } catch (Exception e) {
        throw e;
    }
}

استخدِم ميزة "إدخال البيانات في الدالة الإنشائية" لإنشاء مثيل لفئة الخدمة في فئة وحدة التحكّم.

/** Declare AuthService to be used in the Controller class constructor. */
private final AuthService authService;

/** AuthController constructor. Uses constructor injection to instantiate
*   the AuthService and UserRepository classes.
*   @param authService the service class that handles the implementation logic
*   of requests.
*/
public AuthController(AuthService authService) {
    this.authService = authService;
}

أضِف نقطة النهاية /authorize إلى فئة وحدة التحكّم. تستدعي نقطة النهاية هذه الطريقة authorize() في AuthService لاسترداد المَعلمة state وعنوان URL الخاص بالتفويض. بعد ذلك، تخزِّن نقطة النهاية المَعلمة state في الجلسة وتعيد توجيه المستخدمين إلى عنوان URL الخاص بالمصادقة.

/** Redirects the sign-in pop-up to the authorization URL.
*   @param response the current response to pass information to.
*   @param session the current session.
*   @throws Exception if redirection to the authorization URL is unsuccessful.
*/
@GetMapping(value = {"/authorize"})
public void authorize(HttpServletResponse response, HttpSession session)
    throws Exception {
    try {
        HashMap authDataMap = authService.authorize();
        String authUrl = authDataMap.get("url").toString();
        String state = authDataMap.get("state").toString();
        session.setAttribute("state", state);
        response.sendRedirect(authUrl);
    } catch (Exception e) {
        throw e;
    }
}

التعامل مع استجابة الخادم

بعد التفويض، يعود المستخدم إلى المسار redirect_uri من الخطوة السابقة. في المثال السابق، يكون هذا المسار /callback.

تتلقّى code في الردّ عندما يعود المستخدم من صفحة التفويض. بعد ذلك، بدِّل الرمز المميز برموز الدخول وإعادة التحميل:

Python

أضِف عمليات الاستيراد التالية إلى ملف خادم Flask.

import google.oauth2.credentials
import googleapiclient.discovery

أضِف المسار إلى خادمك. أنشئ مثيلاً آخر من google_auth_oauthlib.flow.Flow، ولكن أعِد استخدام الحالة التي تم حفظها في الخطوة السابقة.

@app.route("/callback")
def callback():
    state = flask.session["state"]

    flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
        CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
    flow.redirect_uri = flask.url_for("callback", _external=True)

بعد ذلك، اطلب رموز الدخول وإعادة التحميل. لحسن الحظ، يحتوي الكائن flow أيضًا على الطريقة fetch_token لتنفيذ ذلك. تتوقّع الطريقة إما الوسيطة code أو الوسيطة authorization_response. استخدِم authorization_response، لأنّه عنوان URL الكامل من الطلب.

authorization_response = flask.request.url
flow.fetch_token(authorization_response=authorization_response)

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

credentials = flow.credentials
flask.session["credentials"] = {
    "token": credentials.token,
    "refresh_token": credentials.refresh_token,
    "token_uri": credentials.token_uri,
    "client_id": credentials.client_id,
    "client_secret": credentials.client_secret,
    "scopes": credentials.scopes
}

# Close the pop-up by rendering an HTML page with a script that redirects
# the owner and closes itself. This can be done with a bit of JavaScript:
# <script>
#     window.opener.location.href = "{{ url_for('classroom_addon') }}";
#     window.close();
# </script>
return flask.render_template("close-me.html")

Java

أضِف طريقة إلى فئة الخدمة تعرض عنصر Credentials من خلال تمرير رمز التفويض الذي تم استرداده من عملية إعادة التوجيه التي تم تنفيذها بواسطة عنوان URL الخاص بالتفويض. يتم استخدام عنصر Credentials هذا لاحقًا لاسترداد رمز الدخول ورمز إعادة التحميل.

/** Returns the required credentials to access Google APIs.
*   @param authorizationCode the authorization code provided by the
*   authorization URL that's used to obtain credentials.
*   @return the credentials that were retrieved from the authorization flow.
*   @throws Exception if retrieving credentials is unsuccessful.
*/
public Credential getAndSaveCredentials(String authorizationCode) throws Exception {
    try {
        GoogleAuthorizationCodeFlow flow = getFlow();
        GoogleClientSecrets googleClientSecrets = getClientSecrets();
        TokenResponse tokenResponse = flow.newTokenRequest(authorizationCode)
            .setClientAuthentication(new ClientParametersAuthentication(
                googleClientSecrets.getWeb().getClientId(),
                googleClientSecrets.getWeb().getClientSecret()))
            .setRedirectUri(REDIRECT_URI)
            .execute();
        Credential credential = flow.createAndStoreCredential(tokenResponse, null);
        return credential;
    } catch (Exception e) {
        throw e;
    }
}

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

بعد ذلك، استدعِ طريقة AuthService getAndSaveCredentials وأدخِل رمز التفويض كمَعلمة. بعد استرداد العنصر Credentials ، خزِّنه في الجلسة. بعد ذلك، أغلِق مربّع الحوار وأعِد توجيه المستخدم إلى الصفحة المقصودة للإضافة.

/** Handles the redirect URL to grant the application access to the user's
*   account.
*   @param request the current request used to obtain the authorization code
*   and state parameter from.
*   @param session the current session.
*   @param response the current response to pass information to.
*   @param model the Model interface to pass error information that's
*   displayed on the error page.
*   @return the close-pop-up template if authorization is successful, or the
*   onError method to handle and display the error message.
*/
@GetMapping(value = {"/callback"})
public String callback(HttpServletRequest request, HttpSession session,
    HttpServletResponse response, Model model) {
    try {
        String authCode = request.getParameter("code");
        String requestState = request.getParameter("state");
        String sessionState = session.getAttribute("state").toString();
        if (!requestState.equals(sessionState)) {
            response.setStatus(401);
            return onError("Invalid state parameter.", model);
        }
        Credential credentials = authService.getAndSaveCredentials(authCode);
        session.setAttribute("credentials", credentials);
        return "close-pop-up";
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

اختبار طلب بيانات من واجهة برمجة التطبيقات

بعد إكمال عملية الربط، يمكنك الآن إرسال طلبات إلى واجهات Google API.

على سبيل المثال، اطلب معلومات الملف الشخصي للمستخدم. يمكنك طلب معلومات المستخدم من واجهة برمجة تطبيقات OAuth 2.0.

Python

اطّلِع على مستندات واجهة برمجة التطبيقات OAuth 2.0 Discovery API واستخدمها للحصول على عنصر UserInfo مملوء.

# Retrieve the credentials from the session data and construct a
# Credentials instance.
credentials = google.oauth2.credentials.Credentials(
    **flask.session["credentials"])

# Construct the OAuth 2.0 v2 discovery API library.
user_info_service = googleapiclient.discovery.build(
    serviceName="oauth2", version="v2", credentials=credentials)

# Request and store the username in the session.
# This allows it to be used in other methods or in an HTML template.
flask.session["username"] = (
    user_info_service.userinfo().get().execute().get("name"))

Java

أنشئ طريقة في فئة الخدمة تنشئ كائن UserInfo باستخدام Credentials كمعلَمة.

/** Obtains the Userinfo object by passing in the required credentials.
*   @param credentials retrieved from the authorization flow.
*   @return the Userinfo object for the currently signed-in user.
*   @throws IOException if creating UserInfo service or obtaining the
*   Userinfo object is unsuccessful.
*/
public Userinfo getUserInfo(Credential credentials) throws IOException {
    try {
        Oauth2 userInfoService = new Oauth2.Builder(
            new NetHttpTransport(),
            new GsonFactory(),
            credentials).build();
        Userinfo userinfo = userInfoService.userinfo().get().execute();
        return userinfo;
    } catch (Exception e) {
        throw e;
    }
}

أضِف نقطة النهاية /test إلى وحدة التحكّم التي تعرض البريد الإلكتروني للمستخدم.

/** Returns the test request page with the user's email.
*   @param session the current session.
*   @param model the Model interface to pass error information that's
*   displayed on the error page.
*   @return the test page that displays the current user's email or the
*   onError method to handle and display the error message.
*/
@GetMapping(value = {"/test"})
public String test(HttpSession session, Model model) {
    try {
        Credential credentials = (Credential) session.getAttribute("credentials");
        Userinfo userInfo = authService.getUserInfo(credentials);
        String userInfoEmail = userInfo.getEmail();
        if (userInfoEmail != null) {
            model.addAttribute("userEmail", userInfoEmail);
        } else {
            return onError("Could not get user email.", model);
        }
        return "test";
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

محو بيانات الاعتماد

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

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

Python

@app.route("/clear")
def clear_credentials():
    if "credentials" in flask.session:
        del flask.session["credentials"]
        del flask.session["username"]

    return flask.render_template("signed-out.html")

بدلاً من ذلك، استخدِم flask.session.clear()، ولكن قد يؤدي ذلك إلى آثار غير مقصودة إذا كانت لديك قيم أخرى مخزّنة في الجلسة.

Java

في وحدة التحكّم، أضِف نقطة نهاية /clear.

/** Clears the credentials in the session and returns the sign-out
*   confirmation page.
*   @param session the current session.
*   @return the sign-out confirmation page.
*/
@GetMapping(value = {"/clear"})
public String clear(HttpSession session) {
    try {
        if (session != null && session.getAttribute("credentials") != null) {
            session.removeAttribute("credentials");
        }
        return "sign-out";
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

إبطال إذن التطبيق

يمكن للمستخدم إبطال إذن تطبيقك عن طريق إرسال طلب POST إلى https://oauth2.googleapis.com/revoke. يجب أن يتضمّن الطلب رمز الدخول الخاص بالمستخدم.

Python

import requests

@app.route("/revoke")
def revoke():
    if "credentials" not in flask.session:
        return flask.render_template("addon-discovery.html",
                            message="You need to authorize before " +
                            "attempting to revoke credentials.")

    credentials = google.oauth2.credentials.Credentials(
        **flask.session["credentials"])

    revoke = requests.post(
        "https://oauth2.googleapis.com/revoke",
        params={"token": credentials.token},
        headers={"content-type": "application/x-www-form-urlencoded"})

    if "credentials" in flask.session:
        del flask.session["credentials"]
        del flask.session["username"]

    status_code = getattr(revoke, "status_code")
    if status_code == 200:
        return flask.render_template("authorization.html")
    else:
        return flask.render_template(
            "index.html", message="An error occurred during revocation!")

Java

أضِف طريقة إلى فئة الخدمة التي تجري طلبًا إلى نقطة نهاية الإبطال.

/** Revokes the app's permissions to the user's account.
*   @param credentials retrieved from the authorization flow.
*   @return response entity returned from the HTTP call to obtain response
*   information.
*   @throws RestClientException if the POST request to the revoke endpoint is
*   unsuccessful.
*/
public ResponseEntity<String> revokeCredentials(Credential credentials) throws RestClientException {
    try {
        String accessToken = credentials.getAccessToken();
        String url = "https://oauth2.googleapis.com/revoke?token=" + accessToken;

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
        HttpEntity<Object> httpEntity = new HttpEntity<Object>(httpHeaders);
        ResponseEntity<String> responseEntity = new RestTemplate().exchange(
            url,
            HttpMethod.POST,
            httpEntity,
            String.class);
        return responseEntity;
    } catch (RestClientException e) {
        throw e;
    }
}

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

/** Revokes the app's permissions and returns the authorization page.
*   @param session the current session.
*   @return the authorization page.
*   @throws Exception if revoking access is unsuccessful.
*/
@GetMapping(value = {"/revoke"})
public String revoke(HttpSession session) throws Exception {
    try {
        if (session != null && session.getAttribute("credentials") != null) {
            Credential credentials = (Credential) session.getAttribute("credentials");
            ResponseEntity responseEntity = authService.revokeCredentials(credentials);
            Integer httpStatusCode = responseEntity.getStatusCodeValue();

            if (httpStatusCode != 200) {
                return onError("There was an issue revoking access: " +
                    responseEntity.getStatusCode(), model);
            }
            session.removeAttribute("credentials");
        }
        return startAuthFlow(model);
    } catch (Exception e) {
        return onError(e.getMessage(), model);
    }
}

اختبار الإضافة

سجِّل الدخول إلى Google Classroom بصفتك أحد المستخدمين التجريبيين المعلِّمين. انتقِل إلى علامة التبويب الواجب الدراسي وأنشِئ مهمة جديدة. انقر على زر الإضافات أسفل مساحة النص، ثم اختَر الإضافة. يتم فتح إطار iframe وتحمّل الإضافة معرّف الموارد المنتظم لإعداد المرفقات الذي حدّدته في صفحة إعداد التطبيق في حزمة تطوير البرامج (SDK) الخاصة بـ GWM.

تهانينا! أنت الآن جاهز للانتقال إلى الخطوة التالية: التعامل مع الزيارات المتكرّرة إلى الإضافة.