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

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

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

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

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

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

فهم مسار المصادقة

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

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

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

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

اضبط redirect_uri في flow، وهو المسار الذي تريد أن يعود إليه المستخدِمون بعد تفويض تطبيقك. يُرجى العِلم أنّه /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 لإنشاء مثيل لعنصر flow، ثم استخدِمه لاسترداد عنوان URL المخصص لمنح الإذن:

  • تقرأ طريقة getClientSecrets() ملف سر العميل وتُنشئ كائن GoogleClientSecrets.
  • تنشئ طريقة getFlow() مثيلًا من GoogleAuthorizationCodeFlow.
  • تستخدِم طريقة authorize() عنصر GoogleAuthorizationCodeFlow ومَعلمة state وعنوان URL لإعادة التوجيه لاسترداد عنوان 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 إلى فئة وحدة التحكّم. تستدعي نقطة النهاية هذه طريقة AuthService authorize() لاسترداد المَعلمة 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;
    }
}

أضِف نقطة نهاية لعنوان URL لإعادة التوجيه إلى وحدة التحكّم. استرداد رمز التفويض ومَعلمة 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 APIs.

على سبيل المثال، يمكنك طلب معلومات الملف الشخصي للمستخدم. يمكنك طلب معلومات المستخدم من واجهة برمجة التطبيقات 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 وتحميل الإضافة عنوان URL لإعداد المرفق الذي حدّدته في صفحة إعدادات التطبيق لإطار عمل GWM SDK.

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