OAuth 2.0 و Google OAuth Client Library برای جاوا

بررسی اجمالی

هدف: این سند توابع عمومی OAuth 2.0 ارائه شده توسط Google OAuth Client Library برای جاوا را شرح می دهد. می توانید از این توابع برای احراز هویت و مجوز برای هر سرویس اینترنتی استفاده کنید.

برای دستورالعمل‌های استفاده از GoogleCredential برای انجام مجوز OAuth 2.0 با خدمات Google، به استفاده از OAuth 2.0 با Google API Client Library برای جاوا مراجعه کنید.

خلاصه: OAuth 2.0 یک مشخصات استاندارد است که به کاربران نهایی اجازه می دهد تا به طور ایمن به یک برنامه مشتری برای دسترسی به منابع محافظت شده سمت سرور مجوز دهند. علاوه بر این، مشخصات توکن حامل OAuth 2.0 نحوه دسترسی به منابع محافظت شده را با استفاده از توکن دسترسی اعطا شده در طول فرآیند مجوز کاربر نهایی توضیح می دهد.

برای جزئیات، به مستندات Javadoc برای بسته های زیر مراجعه کنید:

ثبت نام مشتری

قبل از استفاده از Google OAuth Client Library برای جاوا، احتمالاً باید برنامه خود را در سرور مجوز ثبت کنید تا شناسه مشتری و راز مشتری را دریافت کنید. (برای اطلاعات کلی در مورد این فرآیند، مشخصات ثبت مشتری را ببینید.)

فروشگاه شناسنامه و اعتبار

Credential یک کلاس کمکی 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 Client Library برای جاوا ارائه شده است.

می توانید از یکی از پیاده سازی های زیر که توسط کتابخانه ارائه شده است استفاده کنید:

  • 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) استفاده شود.
  • از AuthorizationCodeFlow.createAndStoreCredential(TokenResponse، String) برای ذخیره و دریافت اعتبار برای دسترسی به منابع محافظت شده استفاده کنید.

همچنین، اگر از AuthorizationCodeFlow استفاده نمی‌کنید، می‌توانید از کلاس‌های سطح پایین‌تر استفاده کنید:

جریان کد مجوز Servlet

این کتابخانه کلاس های کمکی servlet را برای ساده کردن قابل توجه جریان کد مجوز برای موارد استفاده اساسی ارائه می دهد. شما فقط زیر کلاس های مشخص 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 تقریباً مشابه جریان کد مجوز سرورلت است، با این تفاوت که ما می‌توانیم از API جاوا کاربران Google App Engine استفاده کنیم. کاربر باید وارد سیستم شود تا Users Java API فعال شود. برای اطلاعات در مورد هدایت مجدد کاربران به صفحه ورود به سیستم در صورتی که قبلاً وارد سیستم نشده‌اند، به امنیت و احراز هویت (در web.xml) مراجعه کنید.

تفاوت اصلی با مورد servlet این است که شما زیر کلاس های مشخص AbstractAppEngineAuthorizationCodeServlet و AbstractAppEngineAuthorizationCodeCallbackServlet (از google-oauth-client-appengine ) ارائه می دهید. آنها کلاس های سرولت انتزاعی را گسترش داده و با استفاده از Users Java API متد getUserId را برای شما پیاده سازی می کنند. AppEngineDataStoreFactory (از Google HTTP Client Library برای جاوا گزینه خوبی برای حفظ اعتبار با استفاده از 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 ، مرورگر کاربر نهایی را به صفحه مجوز هدایت کنید، جایی که کاربر نهایی می تواند به برنامه شما اجازه دسترسی به داده های محافظت شده خود را بدهد.
  • از یک برنامه جاوا اسکریپت برای پردازش نشانه دسترسی یافت شده در قطعه 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 ، هنگامی که سرور برای دسترسی به یک منبع محافظت شده با یک نشانه دسترسی منقضی شده فراخوانی می شود، سرور معمولاً با یک کد وضعیت 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 ادامه دهید. این استراتژی به طور پیش فرض در Credential استفاده می شود.

گزینه دیگر این است که قبل از هر درخواست یک توکن دسترسی جدید بگیرید، اما هر بار نیاز به یک درخواست HTTP اضافی به سرور توکن دارد، بنابراین احتمالاً از نظر سرعت و استفاده از شبکه انتخاب ضعیفی است. در حالت ایده‌آل، رمز دسترسی را در فضای ذخیره‌سازی ایمن و دائمی ذخیره کنید تا درخواست‌های یک برنامه برای توکن‌های دسترسی جدید به حداقل برسد. (اما برای برنامه های نصب شده، ذخیره سازی ایمن یک مشکل دشوار است.)

توجه داشته باشید که نشانه دسترسی ممکن است به دلایلی غیر از انقضا نامعتبر شود، به عنوان مثال اگر کاربر به صراحت رمز را باطل کرده باشد، بنابراین مطمئن شوید که کد رسیدگی به خطا شما قوی است. هنگامی که متوجه شدید که یک رمز دیگر معتبر نیست، به عنوان مثال اگر منقضی شده یا لغو شده است، باید رمز دسترسی را از فضای ذخیره‌سازی خود حذف کنید. به عنوان مثال، در Android، باید با AccountManager.invalidateAuthToken تماس بگیرید.