OAuth 2.0 ومكتبة برامج Google OAuth للغة Java

نظرة عامة

الغرض: يصف هذا المستند وظائف OAuth 2.0 العامة التي تقدّمها. مكتبة عميل Google OAuth للغة Java. يمكنك استخدام هذه الدوال المصادقة والترخيص لأية خدمة من خدمات الإنترنت.

للحصول على تعليمات حول استخدام GoogleCredential لتنفيذ تفويض OAuth 2.0 باستخدام خدمات Google، يُرجى الاطّلاع على استخدام OAuth 2.0 مع مكتبة برامج Google API للغة Java

ملخص: OAuth 2.0 هو المواصفات القياسية للسماح للمستخدمين النهائيين بمنح تفويض آمن لأحد العملاء الوصول إلى الموارد المحمية من جانب الخادم. بالإضافة إلى ذلك، رمز حامل OAuth 2.0 المميز كيفية الوصول إلى تلك الموارد المحمية من خلال الرمز المميز الذي تم منحه أثناء عملية تفويض المستخدم النهائي.

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

تسجيل العميل

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

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

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

  public static HttpResponse executeGet(
      HttpTransport transport, JsonFactory jsonFactory, String accessToken, GenericUrl url)
      throws IOException {
    Credential credential =
        new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(accessToken);
    HttpRequestFactory requestFactory = transport.createRequestFactory(credential);
    return requestFactory.buildGetRequest(url).execute();
  }

تحتاج معظم التطبيقات إلى الاحتفاظ برمز الدخول إلى بيانات الاعتماد يُرجى إعادة تحميل الرمز المميّز لتجنُّب إعادة التوجيه في المستقبل إلى التفويض. في المتصفح. تشير رسالة الأشكال البيانية CredentialStore تم إيقاف أداة التنفيذ في هذه المكتبة نهائيًا وستتم إزالتها في المستقبل والإصدارات. البديل هو استخدام DataStoreFactory وDataStore الواجهات مع StoredCredential، والتي توفرها مكتبة برامج Google HTTP للغة Java

يمكنك استخدام أحد الإجراءات التالية التي توفّرها المكتبة:

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

مستخدمو Google App Engine:

تم إيقاف AppEngineCredentialStore نهائيًا وتجري إزالتها.

ننصحك باستخدام AppEngineDataStoreFactory باستخدام StoredCredential. إذا كانت لديك بيانات اعتماد مخزَّنة بالطريقة القديمة، يمكنك استخدام طرق المساعدة المضافة. migrateTo(AppEngineDataStoreFactory) أو migrateTo(DataStore) لنقل البيانات.

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

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

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

يتم تنفيذ هذا التدفق باستخدام AuthorizationCodeFlow: الخطوات كالآتي:

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

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

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

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

نموذج التعليمات البرمجية:

public class ServletSample 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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

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

public class ServletCallbackSample 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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .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 للغة Java) خيارًا جيدًا للاحتفاظ ببيانات الاعتماد باستخدام واجهة برمجة التطبيقات Google App Engine Data Store API.

نموذج التعليمات البرمجية:

public class AppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

public class AppEngineCallbackSample extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  @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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

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

مثال على رمز مبسَّط مأخوذ من dailymotion-cmdline-sample:

/** Authorizes the installed application to access user's protected data. */
private static Credential authorize() throws Exception {
  OAuth2ClientCredentials.errorIfNotSpecified();
  // set up authorization code flow
  AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken
      .authorizationHeaderAccessMethod(),
      HTTP_TRANSPORT,
      JSON_FACTORY,
      new GenericUrl(TOKEN_SERVER_URL),
      new ClientParametersAuthentication(
          OAuth2ClientCredentials.API_KEY, OAuth2ClientCredentials.API_SECRET),
      OAuth2ClientCredentials.API_KEY,
      AUTHORIZATION_SERVER_URL).setScopes(Arrays.asList(SCOPE))
      .setDataStoreFactory(DATA_STORE_FACTORY).build();
  // authorize
  LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost(
      OAuth2ClientCredentials.DOMAIN).setPort(OAuth2ClientCredentials.PORT).build();
  return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}

private static void run(HttpRequestFactory requestFactory) throws IOException {
  DailyMotionUrl url = new DailyMotionUrl("https://api.dailymotion.com/videos/favorites");
  url.setFields("id,tags,title,url");

  HttpRequest request = requestFactory.buildGetRequest(url);
  VideoFeed videoFeed = request.execute().parseAs(VideoFeed.class);
  ...
}

public static void main(String[] args) {
  ...
  DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
  final Credential credential = authorize();
  HttpRequestFactory requestFactory =
      HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
        @Override
        public void initialize(HttpRequest request) throws IOException {
          credential.initialize(request);
          request.setParser(new JsonObjectParser(JSON_FACTORY));
        }
      });
  run(requestFactory);
  ...
}

مسار العميل المستند إلى المتصفح

وهذه هي الخطوات المعتادة لمسار العميل المستند إلى المتصفّح والمحدّد في مواصفات المنح الضمنية:

  • باستخدام BrowserClientRequestUrl، إعادة توجيه متصفّح المستخدم النهائي إلى صفحة التفويض حيث يمكن لمنح تطبيقك حق الدخول إلى بياناته المحمية.
  • استخدام تطبيق JavaScript لمعالجة رمز الدخول الوارد في عنوان URL في عنوان URI لإعادة التوجيه والمسجّل في خادم التفويض.

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

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  String url = new BrowserClientRequestUrl(
      "https://server.example.com/authorize", "s6BhdRkqt3").setState("xyz")
      .setRedirectUri("https://client.example.com/cb").build();
  response.sendRedirect(url);
}

اكتشاف رمز دخول منتهي الصلاحية

وفقًا لمواصفات حامل OAuth 2.0، عند استدعاء الخادم للوصول إلى مورد محمي ذي وصول منتهي الصلاحية أي رمز، يستجيب الخادم عادةً برمز حالة HTTP 401 Unauthorized مثل ما يلي:

   HTTP/1.1 401 Unauthorized
   WWW-Authenticate: Bearer realm="example",
                     error="invalid_token",
                     error_description="The access token expired"

ومع ذلك، يبدو أنّ هناك قدرًا كبيرًا من المرونة في المواصفات. بالنسبة التفاصيل، فراجع مستندات موفّر OAuth 2.0.

يمكنك بدلاً من ذلك التحقّق من معلَمة expires_in في استجابة رمز الدخول. يحدد هذا الخيار المدة بالثواني لرمز الدخول الذي تم منحه، وهو عادةً ساعة. ومع ذلك، قد لا تنتهي صلاحية رمز الدخول فعليًا في النهاية. من هذه الفترة، وقد يستمر الخادم في السماح بالوصول. لهذا السبب في انتظار رمز الحالة 401 Unauthorized، بدلاً من على افتراض انتهاء صلاحية الرمز المميز بناءً على الوقت المنقضي. بدلاً من ذلك، يمكنك حاول تحديث رمز الدخول قبل انتهاء صلاحيته بفترة قصيرة، وإذا كان خادم الرمز غير متاح، يُرجى مواصلة استخدام رمز الدخول حتى تحصل على 401. هذا النمط هي الإستراتيجية المستخدمة افتراضيًا في بيانات الاعتماد:

هناك خيار آخر وهو الحصول على رمز دخول جديد قبل كل طلب، ولكن طلب HTTP إضافي إلى خادم الرمز المميز في كل مرة، لذا من المحتمل اختيار سيئ من حيث السرعة واستخدام الشبكة. سعيًا إلى المثالية، تخزين رمز الدخول في مساحة تخزين آمنة ودائمة لتقليل طلبات التطبيق للوصول الجديد الرموز المميزة. (ولكن بالنسبة للتطبيقات المثبتة، يمثل التخزين الآمن مشكلة صعبة).

لاحظ أن رمز الدخول قد يصبح غير صالح لأسباب أخرى غير انتهاء الصلاحية. على سبيل المثال، إذا أبطل المستخدم الرمز المميز بشكل صريح، لذا رمز معالجة الأخطاء قوي. بعد اكتشاف أنّ الرمز المميّز لم يعُد صالحة، على سبيل المثال، إذا انتهت صلاحيتها أو تم إبطالها، يجب إزالة إمكانية الوصول من مساحة التخزين لديك. على Android، على سبيل المثال، يجب عليك استدعاء إجراء AccountManager.invalidateAuthToken: