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

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

اضبط 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 لإنشاء مثيل لكائن التدفق، ثم استخدِمه لاسترداد عنوان URL للتفويض:

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

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

بعد ذلك، يمكنك استدعاء طريقة getAndSaveCredentials في AuthService وأدخِل رمز التفويض كمَعلمة. بعد استرداد الكائن 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 استخدمها للحصول على كائن 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، وتحمِّل الإضافة معرّف الموارد المنتظم (URI) لإعداد مُرفَقات الذي حدّدته في صفحة إعداد التطبيق لحزمة GWM SDK.

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