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

نظرة عامة

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

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

تم تضمين حزم OAuth 2.0 في مكتبة برامج Google API للغة Java. والغرض العام مكتبة برامج Google OAuth 2.0 للغة Java.

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

وحدة تحكّم Google API

قبل أن تتمكن من الوصول إلى Google APIs، يجب إعداد مشروع على وحدة التحكم في واجهة 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. على عكس بيانات الاعتماد التي يطلب فيها أحد تطبيقات العميل الدخول إلى بيانات المستخدم النهائي، توفر واجهة برمجة تطبيقات هوية التطبيق إمكانية الوصول إلى بيانات العميل البيانات الخاصة بالتطبيق.

استخدام 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(AppEngineDataStoreFactory) أو migrateTo(DataStore) لإجراء الترحيل.

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

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

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

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

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

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

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

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

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

توفر هذه المكتبة فئات serlet مساعدًا لتبسيط عملية مسار رمز التفويض لحالات الاستخدام الأساسية. ما عليك سوى تقديم فئات فرعية ملموسة من 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 متطابق تقريبًا مع سيرفلت مسار شفرة التفويض، إلا أننا نستطيع الاستفادة من واجهة برمجة تطبيقات المستخدمون Java API: المستخدِم تسجيل الدخول حتى يتم تفعيل واجهة برمجة تطبيقات Java للمستخدمين للحصول على معلومات عن إعادة توجيه المستخدمين إلى صفحة تسجيل دخول إذا لم يسبق لهم تسجيل الدخول، فراجع الأمان والمصادقة (في web.xml).

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

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

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

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