استخدام OAuth 2.0 مع مكتبة برامج Google API للغة Java

نظرة عامة

الغرض: يشرح هذا المستند كيفية استخدام فئة الأداة GoogleCredential لإجراء تفويض OAuth 2.0 مع خدمات Google. للحصول على معلومات عن وظائف OAuth 2.0 العامة التي نقدمها، يمكنك الاطّلاع على OAuth 2.0 وOAuth 2.0 وGoogle OAuth Client Library (مكتبة برامج OAuth من Google) للغة Java.

الملخّص: للوصول إلى البيانات المحمية المخزَّنة على خدمات Google، استخدِم OAuth 2.0 للحصول على الإذن. تتوافق واجهات برمجة تطبيقات Google مع مسارات OAuth 2.0 لأنواع مختلفة من تطبيقات العميل. في جميع هذه العمليات، يطلب تطبيق العميل رمز دخول مرتبطًا بتطبيق العميل فقط ومالك البيانات المحمية التي يتم الوصول إليها. يرتبط رمز الدخول أيضًا بنطاق محدود يحدد نوع البيانات التي يمكن لتطبيق العميل الوصول إليها (على سبيل المثال "إدارة مهامك"). من الأهداف المهمة لبروتوكول OAuth 2.0 توفير وصول آمن ومريح إلى البيانات المحمية، مع تقليل التأثير المحتمل في حال سرقة رمز الدخول.

تم إنشاء حِزم OAuth 2.0 في مكتبة برامج Google API للغة Java استنادًا إلى مكتبة عملاء Google OAuth 2.0 للغة Java للأغراض العامة.

للاطلاع على التفاصيل، راجع وثائق JavaDoc حول الحزم التالية:

وحدة تحكّم Google API

قبل أن تتمكن من الوصول إلى واجهات برمجة تطبيقات Google، يجب إعداد مشروع على وحدة التحكم في واجهة Google API لأغراض المصادقة والفوترة، سواء كان العميل تطبيقًا مثبّتًا أم تطبيقًا متوافقًا مع الأجهزة الجوّالة أم خادمًا ويب أم عميلاً يتم تشغيله في المتصفح.

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

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

GoogleCredential

GoogleCredential عبارة عن فئة مساعد آمنة من سلاسل التعليمات لـ OAuth 2.0 للوصول إلى الموارد المحمية باستخدام رمز دخول. على سبيل المثال، إذا كان لديك رمز الدخول، يمكنك تقديم طلب بالطريقة التالية:

GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Plus plus = new Plus.builder(new NetHttpTransport(),
                             GsonFactory.getDefaultInstance(),
                             credential)
    .setApplicationName("Google-PlusSample/1.0")
    .build();

هوية Google App Engine

تستند بيانات الاعتماد البديلة هذه إلى Google App Engine App Identity Java API. على عكس بيانات الاعتماد التي يطلب فيها تطبيق العميل الوصول إلى بيانات أحد المستخدمين، توفّر واجهة برمجة التطبيقات App Identity API إمكانية الوصول إلى البيانات الخاصة بتطبيق العميل.

استخدِم AppIdentityCredential (من google-api-client-appengine). إن بيانات الاعتماد هذه أبسط كثيرًا لأن Google App Engine تهتم بجميع التفاصيل. ما عليك سوى تحديد نطاق OAuth 2.0 الذي تحتاجه.

مثال على رمز مأخوذ من urlshortener-robots-appengine-sample:

static Urlshortener newUrlshortener() {
  AppIdentityCredential credential =
      new AppIdentityCredential(
          Collections.singletonList(UrlshortenerScopes.URLSHORTENER));
  return new Urlshortener.Builder(new UrlFetchTransport(),
                                  GsonFactory.getDefaultInstance(),
                                  credential)
      .build();
}

مخزن بيانات

تنتهي صلاحية رمز الدخول عادةً في ساعة واحدة، وبعد ذلك ستتلقّى رسالة خطأ إذا حاولت استخدامه. تتولى GoogleCredential "تحديث" الرمز المميز تلقائيًا، ما يعني ببساطة الحصول على رمز دخول جديد. يحدث ذلك عن طريق إنشاء رمز مميّز طويل الأمد لإعادة التحميل، والذي يتم تلقّيه عادةً مع رمز الدخول في حال كنت تستخدم المَعلمة access_type=offline أثناء مسار رمز التفويض (يمكنك الاطّلاع على GoogleAuthorizationCodeFlow.Builder.setAccessType(String)).

وستحتاج معظم التطبيقات إلى الاحتفاظ برمز الدخول إلى بيانات الاعتماد و/أو تحديث الرمز المميّز. للاحتفاظ بإمكانية الوصول إلى بيانات الاعتماد و/أو الرموز المميّزة لإعادة التحميل، يمكنك تقديم طريقة تنفيذك الخاصة لـ DataStoreFactory مع StoredCredential، أو يمكنك استخدام إحدى عمليات التنفيذ التالية التي توفّرها المكتبة:

  • AppEngineDataStoreFactory: يحتفظ ببيانات الاعتماد باستخدام واجهة برمجة تطبيقات Google App Engine Data Store API.
  • MemoryDataStoreFactory: "تحتفظ" ببيانات الاعتماد في الذاكرة، والتي تكون مفيدة فقط كتخزين قصير الأجل طوال مدة العملية.
  • FileDataStoreFactory: يحتفظ ببيانات الاعتماد في الملف

مستخدمو AppEngine: تم إيقاف AppEngineCredentialStore نهائيًا وستتم إزالته قريبًا. ننصحك باستخدام AppEngineDataStoreFactory مع StoredCredential. إذا كانت لديك بيانات اعتماد مخزّنة بالطريقة القديمة، يمكنك استخدام طريقتَي المساعد المُضافة MigrateTo(AppEngineDataStoreلقد أ) أو MigrateTo(DataStore) لإجراء عملية النقل.

يمكنك استخدام DataStoreCredentialRefreshListener وإعداده لبيانات الاعتماد باستخدام GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener)).

مسار رمز التفويض

استخدِم مسار رمز التفويض للسماح للمستخدم النهائي بمنح تطبيقك إذن الوصول إلى بياناته المحمية على Google APIs. ويتم تحديد بروتوكول هذا المسار في منح رمز التفويض.

ويتم تنفيذ هذا المسار باستخدام GoogleAuthorizationCodeFlow. الخطوات كالآتي:

  • تسجيل دخول المستخدم إلى تطبيقك وستحتاج إلى ربط هذا المستخدِم برقم تعريف مستخدِم فريد لتطبيقك.
  • استدعِ AuthorizationCodeFlow.loadCredential(String)) استنادًا إلى رقم تعريف المستخدم لمعرفة ما إذا كانت بيانات اعتماد المستخدم معروفة أم لا. إذا كان الأمر كذلك، فقد انتهينا.
  • إذا لم يكن الأمر كذلك، يجب استدعاء AuthorizationCodeFlow.newAuthorizationUrl() وتوجيه متصفح المستخدم إلى صفحة التفويض لمنح تطبيقك إمكانية الوصول إلى بياناته المحمية.
  • بعد ذلك سيعيد خادم تفويض Google توجيه المتصفح مرة أخرى إلى عنوان URL لإعادة التوجيه الذي يحدّده تطبيقك، إلى جانب مَعلمة طلب البحث code. استخدِم المعلَمة code لطلب رمز دخول باستخدام AuthorizationCodeFlow.newTokenRequest(String).
  • استخدام AuthCodeFlow.createAndStoreCredential(TokenResponse, String)) لتخزين بيانات اعتماد والحصول عليها للوصول إلى الموارد المحمية.

بدلاً من ذلك، إذا كنت لا تستخدم GoogleAuthorizationCodeFlow، يمكنك استخدام فئات ذات مستوى أقل:

عند إعداد مشروعك في وحدة التحكم في واجهة Google API، يمكنك الاختيار من بين بيانات اعتماد مختلفة، وذلك حسب العملية التي تستخدمها. لمعرفة مزيد من التفاصيل، يُرجى الاطّلاع على إعداد سيناريوَي OAuth 2.0 وسيناريو OAuth 2.0. في ما يلي مقتطفات الرمز لكل من التدفقات.

تطبيقات خادم الويب

بروتوكول هذا المسار موضَّح في استخدام OAuth 2.0 لتطبيقات خادم الويب.

توفر هذه المكتبة فئات مساعدة servlet لتبسيط تدفق كود التفويض لحالات الاستخدام الأساسية بشكل كبير. ما عليك سوى توفير فئتَين فرعيتَين ملموستَين من AbstractAuthorizationCodeServlet وAbstractAuthorizationCodeCallbackServlet (من google-oauth-client-servlet) وإضافتها إلى ملف web.xml الخاص بك. لاحظ أنك لا تزال بحاجة إلى الاهتمام بتسجيل تسجيل دخول المستخدم لتطبيق الويب واستخراج رقم تعريف المستخدم.

public class CalendarServletSample extends AbstractAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(
        new NetHttpTransport(), GsonFactory.getDefaultInstance(),
        "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
        Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

public class CalendarServletCallbackSample extends AbstractAuthorizationCodeCallbackServlet {

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    // handle error
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(
        new NetHttpTransport(), GsonFactory.getDefaultInstance()
        "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
        Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

تطبيقات Google App Engine

تدفق رمز التفويض على App Engine مطابق تقريبًا لتدفق رمز التفويض في servlet، إلا أنه يمكننا الاستفادة من واجهة برمجة تطبيقات Java للمستخدمين في Google App Engine. على المستخدم تسجيل الدخول لتفعيل واجهة برمجة تطبيقات JavaScript للمستخدمين. وللحصول على معلومات حول إعادة توجيه المستخدمين إلى صفحة تسجيل دخول إذا لم يسبق لهم تسجيل الدخول، يمكنك الاطّلاع على صفحة الأمان والمصادقة (في web.xml).

يكمن الاختلاف الأساسي عن حالة servlet في أنّك توفّر فئتين فرعيتين ملموسة من AbstractAppEngineAuthorizationCodeServlet وAbstractAppEngineAuthorizationCodeCallbackServlet (من google-oauth-client-appengine. وتعمل هذه البرامج على توسيع فئات servlet المجردة وتنفيذ طريقة getUserId نيابةً عنك باستخدام Users Java API. AppEngineDataStoreFactory (من google-http-client-appengine) هو خيار جيد للاحتفاظ ببيانات الاعتماد باستخدام واجهة برمجة تطبيقات Google App Engine Data Store API.

مثال مأخوذ (تم تعديله قليلاً) من calendar-appengine-sample:

public class CalendarAppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    return Utils.getRedirectUri(req);
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return Utils.newFlow();
  }
}

class Utils {
  static String getRedirectUri(HttpServletRequest req) {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  static GoogleAuthorizationCodeFlow newFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
        getClientCredential(), Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }
}

public class OAuth2Callback extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  private static final long serialVersionUID = 1L;

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    String nickname = UserServiceFactory.getUserService().getCurrentUser().getNickname();
    resp.getWriter().print("<h3>" + nickname + ", why don't you want to play with me?</h1>");
    resp.setStatus(200);
    resp.addHeader("Content-Type", "text/html");
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    return Utils.getRedirectUri(req);
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return Utils.newFlow();
  }
}

للحصول على عيّنة إضافية، اطّلِع على storage-serviceaccount-appengine-sample.

حسابات الخدمة

تتوافق GoogleCredential أيضًا مع حسابات الخدمة. على عكس بيانات الاعتماد التي يطلب فيها تطبيق العميل الوصول إلى بيانات أحد المستخدمين، توفّر حسابات الخدمة إمكانية الوصول إلى البيانات الخاصة بتطبيق العميل. يوقِّع تطبيق العميل الطلب للحصول على رمز دخول باستخدام مفتاح خاص تم تنزيله من وحدة التحكم في واجهة Google API.

مثال على الرمز مأخوذ من plus-serviceaccount-cmdline-sample:

HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
...
// Build service account credential.

GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json"))
    .createScoped(Collections.singleton(PlusScopes.PLUS_ME));
// Set up global Plus instance.
plus = new Plus.Builder(httpTransport, jsonFactory, credential)
    .setApplicationName(APPLICATION_NAME).build();
...

للاطّلاع على عيّنة إضافية، يُرجى مراجعة storage-serviceaccount-cmdline-sample.

انتحال الهوية

يمكنك أيضًا استخدام إجراءات حساب الخدمة لانتحال هوية مستخدم في نطاق تملكه. ويشبه هذا إلى حد كبير سير حساب الخدمة أعلاه، ولكن يمكنك أيضًا استدعاء GoogleCredential.Builder.setServiceAccountUser(String).

التطبيقات المثبتة

وهذا هو مسار رمز تفويض سطر الأوامر الموضح في استخدام OAuth 2.0 للتطبيقات المثبّتة.

مثال على مقتطف من plus-cmdline-sample:

public static void main(String[] args) {
  try {
    httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR);
    // authorization
    Credential credential = authorize();
    // set up global Plus instance
    plus = new Plus.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName(
        APPLICATION_NAME).build();
   // ...
}

private static Credential authorize() throws Exception {
  // load client secrets
  GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
      new InputStreamReader(PlusSample.class.getResourceAsStream("/client_secrets.json")));
  // set up authorization code flow
  GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
      httpTransport, JSON_FACTORY, clientSecrets,
      Collections.singleton(PlusScopes.PLUS_ME)).setDataStoreFactory(
      dataStoreFactory).build();
  // authorize
  return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
}

التطبيقات من جهة العميل

لاستخدام تدفق البرنامج المستند إلى المتصفّح والموضَّح في استخدام OAuth 2.0 للتطبيقات من جهة العميل، ستحتاج عادةً إلى اتّباع الخطوات التالية:

  1. يمكنك إعادة توجيه المستخدم في المتصفح إلى صفحة التفويض باستخدام GoogleBrowserClientRequestUrl لمنح تطبيق المتصفح إمكانية الوصول إلى بيانات المستخدم المحمية المحمية.
  2. استخدِم مكتبة برامج Google API للغة JavaScript لمعالجة رمز الدخول في جزء عنوان URL في معرّف الموارد المنتظم (URI) لإعادة التوجيه المسجّل في وحدة تحكّم Google API.

مثال لاستخدام تطبيق ويب:

public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException {
  String url = new GoogleBrowserClientRequestUrl("812741506391.apps.googleusercontent.com",
      "https://oauth2.example.com/oauthcallback", Arrays.asList(
          "https://www.googleapis.com/auth/userinfo.email",
          "https://www.googleapis.com/auth/userinfo.profile")).setState("/profile").build();
  response.sendRedirect(url);
}

Android

@إصدار تجريبي

ما هي المكتبة التي يمكن استخدامها مع Android:

إذا كنت تطوِّر تطبيقك لنظام التشغيل Android وكانت واجهة برمجة تطبيقات Google التي تريد استخدامها مضمّنة في مكتبة خدمات Google Play، استخدِم تلك المكتبة للحصول على أفضل أداء وتجربة. إذا كانت واجهة برمجة تطبيقات Google التي تريد استخدامها مع Android ليست جزءًا من مكتبة خدمات Google Play، يمكنك استخدام مكتبة برامج Google API للغة Java، والتي تتوافق مع نظام التشغيل Android 4.0 (Ice كريم ساندويتش) (أو إصدار أحدث)، كما هو موضح هنا. إنّ دعم Android في مكتبة برامج "واجهة Google API" للغة Java هو @إصدار تجريبي.

الخلفية:

بدءًا من إصدار Eclair (حزمة SDK 2.1)، تتم إدارة حسابات المستخدمين على جهاز Android باستخدام مدير الحساب. تتم إدارة جميع تفويضات تطبيقات Android مركزيًا بواسطة SDK باستخدام AccountManager. وتحدد نطاق OAuth 2.0 الذي يحتاجه تطبيقك، ويعرض رمز دخول لاستخدامه.

يتم تحديد نطاق OAuth 2.0 من خلال المَعلمة authTokenType مثل oauth2: بالإضافة إلى النطاق. مثال:

oauth2:https://www.googleapis.com/auth/tasks

تحدد هذه السمة الإذن بالوصول للقراءة/الكتابة إلى واجهة برمجة تطبيقات "مهام Google". إذا كنت بحاجة إلى نطاقات OAuth 2.0 متعددة، استخدِم قائمة مفصولة بمسافات.

تتضمّن بعض واجهات برمجة التطبيقات معلَمات authTokenType خاصة صالحة أيضًا. على سبيل المثال، "إدارة مهامك" هو اسم مستعار لمثال authtokenType الموضح أعلاه.

وعليك أيضًا تحديد مفتاح واجهة برمجة التطبيقات من وحدة التحكم في واجهة Google API. وبخلاف ذلك، يزودك الرمز المميز الذي يمنحه لك AccountManager فقط بحصة مجهولة، والتي عادة ما تكون منخفضة للغاية. على النقيض من ذلك، من خلال تحديد مفتاح واجهة برمجة التطبيقات، فإنك تحصل على حصة مجانية أعلى، ويمكنك اختياريًا إعداد الفوترة للاستخدام الذي يتجاوز ذلك.

مثال على مقتطف رمز مأخوذ من tasks-android-sample:

com.google.api.services.tasks.Tasks service;

@Override
public void onCreate(Bundle savedInstanceState) {
  credential =
      GoogleAccountCredential.usingOAuth2(this, Collections.singleton(TasksScopes.TASKS));
  SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
  credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
  service =
      new com.google.api.services.tasks.Tasks.Builder(httpTransport, jsonFactory, credential)
          .setApplicationName("Google-TasksAndroidSample/1.0").build();
}

private void chooseAccount() {
  startActivityForResult(credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  switch (requestCode) {
    case REQUEST_GOOGLE_PLAY_SERVICES:
      if (resultCode == Activity.RESULT_OK) {
        haveGooglePlayServices();
      } else {
        checkGooglePlayServicesAvailable();
      }
      break;
    case REQUEST_AUTHORIZATION:
      if (resultCode == Activity.RESULT_OK) {
        AsyncLoadTasks.run(this);
      } else {
        chooseAccount();
      }
      break;
    case REQUEST_ACCOUNT_PICKER:
      if (resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) {
        String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME);
        if (accountName != null) {
          credential.setSelectedAccountName(accountName);
          SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
          SharedPreferences.Editor editor = settings.edit();
          editor.putString(PREF_ACCOUNT_NAME, accountName);
          editor.commit();
          AsyncLoadTasks.run(this);
        }
      }
      break;
  }
}