استخدام OAuth 2.0 مع مكتبة عميل Google API لـ Java

نظرة عامة

الغرض: يشرح هذا المستند كيفية استخدام فئة برامج الخدمات GoogleCredential لتفويض OAuth 2.0 مع خدمات Google. للحصول على معلومات عن وظائف بروتوكول OAuth 2.0 العامة التي نقدمها، راجِع OAuth 2.0 ومكتبة برامج Google OAuth لـ Java.

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

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

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

وحدة تحكّم Google API

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

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

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

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

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 إمكانية الوصول إلى بيانات تطبيق العميل.

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

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

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

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

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

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

  • AppEngineDataStoreMan: تظل بيانات الاعتماد باستخدام واجهة برمجة التطبيقات Google App Engine Data Store.
  • MemoryDataStoreMan: "persists"بيانات الاعتماد في الذاكرة التي لا تفيد إلا كوحدة تخزين قصيرة الأجل طوال مدة العملية.
  • FileDataStoreMan: يظل بيانات الاعتماد في ملف.

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

يمكنك استخدام DataStoreCredentialUpdateListener وضبطه لبيانات الاعتماد باستخدام GoogleCredential.Builder.addUpdateListener(CredentialUpdateListener).

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

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

ويتم تنفيذ هذه العملية باستخدام GoogleتفويضCodeFlow. الخطوات كالآتي:

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

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

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

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

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

توفّر هذه المكتبة دروسًا مساعدية من خِدْمَة الخدمات لتبسيط عملية تدفق رمز التفويض لحالات الاستخدام الأساسية. ما عليك سوى تقديم فئات فرعية ملموسة من AbstractAuthenticationCodeServlet وAbstractAuthenticationCodeCallbackServlet (من 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 تقريبًا مع تدفق رمز تفويض ServiceL، باستثناء أنه يمكننا الاستفادة من واجهة برمجة تطبيقات Java للمستخدمين. يحتاج المستخدم إلى تسجيل الدخول حتى يتم تفعيل واجهة برمجة تطبيقات Java للمستخدمين، وللحصول على معلومات حول إعادة توجيه المستخدمين إلى صفحة تسجيل الدخول، إذا لم يسبق لهم تسجيل الدخول، يُرجى الاطّلاع على الأمان والمصادقة (في web.xml).

الفرق الأساسي عن حالة سيفلت هو تقديم فئات فرعية محددة من AbstractAppEngineCodeServlet وAbstractAppEngineCodeCodeCallServlet (من google-oauth-client-app Engine. وتوجّه هذه السياسة فئات الصفوف المجرّدة وتنفّذ الطريقة getUserId نيابةً عنك باستخدام واجهة برمجة تطبيقات Java للمستخدمين. AppEngineDataStoreStore (من google-http-client-app Engine) هو خيار جيد للاحتفاظ ببيانات الاعتماد باستخدام واجهة برمجة التطبيقات App Store في Google App Engine.

مثال مأخوذ (تم تعديله قليلاً) من calendar-app Engine-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-app Engine-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 على عنوان URL لإعادة التوجيه المسجّل في وحدة تحكّم 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" للغة Java إصدار تجريبي.

الخلفية:

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

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

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

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

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

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

مثال على مقتطف رمز مأخوذ من TASKSs-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;
  }
}