OAuth 2.0 ve Java için Google OAuth İstemci Kitaplığı

Genel bakış

Amaç: Bu belgede, Java için Google OAuth İstemci Kitaplığı tarafından sunulan genel OAuth 2.0 işlevleri açıklanmaktadır. Bu işlevleri tüm internet hizmetlerinde kimlik doğrulama ve yetkilendirme amacıyla kullanabilirsiniz.

Google hizmetleriyle OAuth 2.0 yetkilendirmesi yapmak için GoogleCredential kullanma konusunda talimatlar için Java için Google API İstemci Kitaplığı ile OAuth 2.0'ı kullanma bölümüne bakın.

Özet: OAuth 2.0, son kullanıcıların korunan sunucu tarafı kaynaklara erişmesi için bir istemci uygulamasını güvenli bir şekilde yetkilendirmesini sağlayan standart bir spesifikasyondur. Ayrıca OAuth 2.0 hamiline ait jeton spesifikasyonunda, son kullanıcı yetkilendirme sürecinde verilen bir erişim jetonunu kullanarak korunan kaynaklara nasıl erişileceği açıklanmıştır.

Ayrıntılı bilgi için aşağıdaki paketlerin Javadoc dokümanlarına bakın:

Müşteri kaydı

Java için Google OAuth İstemci Kitaplığı'nı kullanmadan önce büyük olasılıkla, istemci kimliği ve istemci gizli anahtarı almak üzere uygulamanızı bir yetkilendirme sunucusuna kaydetmeniz gerekir. (Bu işlemle ilgili genel bilgi için Müşteri Kaydı spesifikasyonuna bakın.)

Kimlik bilgisi deposu

Kimlik bilgisi, erişim jetonu kullanarak korumalı kaynaklara erişmek için iş parçacığı güvenli bir OAuth 2.0 yardımcı sınıfıdır. Yenileme jetonu kullanırken Credential, erişim jetonunun süresi dolduğunda yenileme jetonunu kullanarak erişim jetonunu da yeniler. Örneğin, halihazırda bir erişim jetonunuz varsa aşağıdaki şekilde istekte bulunabilirsiniz:

  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();
  }

Gelecekte tarayıcıdaki yetkilendirme sayfasına yönlendirme yapılmasını önlemek için çoğu uygulamanın kimlik bilgilerinin erişim jetonunu tutması ve yenileme jetonunu tutması gerekir. Bu kitaplıktaki CredentialStore uygulaması kullanımdan kaldırıldı ve gelecekteki sürümlerde kaldırılacaktır. Alternatif olarak, Java için Google HTTP İstemci Kitaplığı tarafından sağlanan StoredCredential ile DataStoreFactory ve DataStore arayüzlerini kullanabilirsiniz.

Kitaplık tarafından sağlanan aşağıdaki uygulamalardan birini kullanabilirsiniz:

Google App Engine kullanıcıları:

AppEngineCredentialStore desteği sonlandırıldı ve kaldırılıyor.

StoredCredential ile AppEngineDataStoreFactory'yi kullanmanızı öneririz. Kimlik bilgilerinizi eski şekilde depoluyorsanız taşıma işlemini gerçekleştirmek için MigrateTo(AppEngineDataStoreFactory) veya migrationTo(DataStore) yardımcı yöntemleri kullanabilirsiniz.

DataStoreCredentialRefreshListener aracını kullanın ve GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) işlevini kullanarak bunu kimlik bilgisi için ayarlayın.

Yetkilendirme kodu akışı

Son kullanıcının, uygulamanızın korunan verilerine erişmesine izin vermek için yetkilendirme kodu akışını kullanın. Bu akışın protokolü, Yetkilendirme Kodu Verme spesifikasyonunda açıklanmıştır.

Bu akış, AuthorizationCodeFlow kullanılarak uygulanır. Adımlar aşağıdaki gibidr:

  • Bir son kullanıcı uygulamanıza giriş yapar. Bu kullanıcıyı uygulamanız için benzersiz bir kullanıcı kimliğiyle ilişkilendirmeniz gerekir.
  • Kullanıcının kimlik bilgilerinin zaten bilinip bilinmediğini kontrol etmek için kullanıcı kimliğine bağlı olarak AuthorizationCodeFlow.loadCredential(String) yöntemini çağırın. Öyleyse, başka bir şey yapmanıza gerek yoktur.
  • Aksi takdirde, AuthorizationCodeFlow.newAuthorizationUrl() öğesini çağırın ve son kullanıcının tarayıcısını, uygulamanızın korunan verilerine erişmesine izin verebileceği bir yetkilendirme sayfasına yönlendirin.
  • Daha sonra web tarayıcısı, yönlendirme URL'sine bir "code" sorgu parametresiyle yönlendirir. Daha sonra bu parametre, AuthorizationCodeFlow.newTokenRequest(String) aracılığıyla erişim jetonu istemek için kullanılabilir.
  • Korunan kaynaklara erişmek için bir kimlik bilgisi depolamak ve kimlik bilgisi almak üzere AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) seçeneğini kullanın.

Alternatif olarak, AuthorizationCodeFlow kullanmıyorsanız alt düzey sınıfları kullanabilirsiniz:

Servlet yetkilendirme kodu akışı

Bu kitaplık, temel kullanım alanları için yetkilendirme kodu akışını önemli ölçüde basitleştirmek için servlet yardımcı sınıfları sağlar. AbstractAuthorizationCodeServlet ve AbstractAuthorizationCodeCallbackServlet'in (google-oauth-client-servlet) somut alt sınıflarını sağlayıp web.xml dosyanıza eklemeniz yeterlidir. Web uygulamanız için kullanıcı girişiyle ilgilenmeniz ve bir kullanıcı kimliği çıkarmanız gerektiğini unutmayın.

Örnek kod:

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 yetkilendirme kodu akışı

App Engine'deki yetkilendirme kodu akışı, servlet yetkilendirme kod akışıyla neredeyse aynıdır. Tek fark, Google App Engine'in Users Java API'sinden yararlanabilmemizdir. Users Java API'nin etkinleştirilmesi için kullanıcının giriş yapması gerekir. Giriş yapmamış kullanıcılar ise kullanıcıları bir giriş sayfasına yönlendirme hakkında daha fazla bilgi için Güvenlik ve Kimlik Doğrulama (web.xml'de) bölümüne bakın.

Servlet durumundan en önemli farkı, AbstractAppEngineAuthorizationCodeServlet ve AbstractAppEngineAuthorizationCodeCallbackServlet (google-oauth-client-appengine'den alınan) somut alt sınıflarını sağlamanızdır. Bu API'ler, soyut servlet sınıflarını genişletir ve Users Java API'yi kullanarak getUserId yöntemini sizin için uygular. AppEngineDataStoreFactory (Java için Google HTTP İstemci Kitaplığı'ndan), Google App Engine Data Store API'sini kullanarak kimlik bilgisini kalıcı tutmak için iyi bir seçenektir.

Örnek kod:

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();
  }
}

Komut satırı yetkilendirme kodu akışı

dailymotion-cmdline-sample öğesinden alınan basitleştirilmiş örnek kod:

/** 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);
  ...
}

Tarayıcı tabanlı istemci akışı

Dolaylı İzin spesifikasyonunda belirtilen tarayıcı tabanlı istemci akışının tipik adımları şunlardır:

  • BrowserClientRequestUrl öğesini kullanarak son kullanıcının tarayıcısını, son kullanıcının korumalı verilerine uygulamanıza erişim izni verebileceği yetkilendirme sayfasına yönlendirin.
  • Yetkilendirme sunucusunda kayıtlı yönlendirme URI'sindeki URL parçasında bulunan erişim jetonunu işlemek için bir JavaScript uygulaması kullanın.

Web uygulaması için örnek kullanım:

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);
}

Süresi dolmuş bir erişim jetonunu algılama

OAuth 2.0 hamiline ait spesifikasyona göre, bir sunucu, süresi dolmuş erişim jetonuna sahip korumalı bir kaynağa erişmek için çağrıldığında, sunucu genellikle aşağıdaki gibi bir HTTP 401 Unauthorized durum koduyla yanıt verir:

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

Ancak, bu spesifikasyonda oldukça fazla esneklik olduğu anlaşılmaktadır. Ayrıntılar için OAuth 2.0 sağlayıcısının belgelerine göz atın.

Alternatif bir yöntem olarak, erişim jetonu yanıtında expires_in parametresini kontrol edebilirsiniz. Bu, verilen erişim jetonunun saniye cinsinden ömrünü (genellikle bir saat) belirtir. Ancak bu sürenin sonunda erişim jetonunun süresi dolmamış olabilir ve sunucu erişim izni vermeye devam edebilir. Bu nedenle, geçen süreye göre jetonun süresinin dolduğunu varsaymak yerine, genellikle bir 401 Unauthorized durum kodu beklemenizi öneririz. Alternatif olarak, erişim jetonunu süresi dolmadan kısa bir süre önce yenilemeyi deneyebilirsiniz. Jeton sunucusu kullanılamıyorsa bir 401 alana kadar erişim jetonunu kullanmaya devam edebilirsiniz. Bu, Kimlik Bilgisi bölümünde varsayılan olarak kullanılan stratejidir.

Diğer bir seçenek de her istekten önce yeni bir erişim jetonu almaktır ancak bu, her seferinde jeton sunucusuna ek bir HTTP isteği yapılmasını gerektirir. Bu nedenle, hız ve ağ kullanımı açısından muhtemelen kötü bir seçimdir. İdeal olarak, bir uygulamanın yeni erişim jetonu isteklerini en aza indirmek için erişim jetonunu güvenli ve kalıcı bir depolama alanında depolayın. (Ancak, yüklenen uygulamalar için güvenli depolama zor bir sorundur.)

Erişim jetonunun, geçerlilik bitiş tarihinden farklı nedenlerle geçersiz hale gelebileceğini unutmayın. Örneğin, kullanıcı jetonu açıkça iptal ettiyse, hata işleme kodunuzun sağlam olduğundan emin olun. Bir jetonun artık geçerli olmadığını (örneğin, süresinin dolması veya iptal edilmesi) tespit ettiğinizde, erişim jetonunu depolama alanınızdan kaldırmanız gerekir. Örneğin, Android'de AccountManager.invalidateAuthToken yöntemini çağırmanız gerekir.