OAuth 2.0'ı Java için Google API İstemci Kitaplığı ile Kullanma

Genel bakış

Amaç: Bu dokümanda, Google hizmetlerinde OAuth 2.0 yetkilendirmesi yapmak için GoogleCredential yardımcı program sınıfının nasıl kullanılacağı açıklanmaktadır. Sağladığımız genel OAuth 2.0 işlevleri hakkında daha fazla bilgi edinmek için OAuth 2.0 ve Java için Google OAuth İstemci Kitaplığı bölümüne göz atın.

Özet: Google hizmetlerinde depolanan korumalı verilere erişmek için yetkilendirme amacıyla OAuth 2.0 kullanın. Google API'leri farklı istemci uygulaması türleri için OAuth 2.0 akışlarını destekler. Bu akışların tümünde istemci uygulaması, yalnızca istemci uygulamanızla ve erişilen korunan verilerin sahibiyle ilişkilendirilmiş bir erişim jetonu ister. Erişim jetonu ayrıca, istemci uygulamanızın erişebildiği veri türlerini tanımlayan sınırlı bir kapsamla da ilişkilendirilir (örneğin, "Görevlerinizi yönetme"). OAuth 2.0 için önemli bir hedef, bir erişim jetonunun çalınması halinde olası etkiyi en aza indirirken, korunan verilere güvenli ve kolay erişim sağlamaktır.

Java için Google API İstemci Kitaplığı'ndaki OAuth 2.0 paketleri, genel amaçlı Java için Google OAuth 2.0 İstemci Kitaplığı temel alınarak geliştirilmiştir.

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

Google API Konsolu

Google API'lerine erişebilmeniz için önce istemcinizin yüklü bir uygulama, mobil uygulama, web sunucusu ya da tarayıcıda çalışan bir istemci olmasına bakılmaksızın kimlik doğrulama ve faturalandırma amacıyla Google API Konsolu'nda bir proje oluşturmanız gerekir.

Kimlik bilgilerinizi doğru şekilde ayarlamayla ilgili talimatlar için API Konsolu Yardımı'na bakın.

Kimlik Bilgisi

GoogleCredential

GoogleCredential, erişim jetonu kullanarak korumalı kaynaklara erişmek için OAuth 2.0 için iş parçacığı güvenli bir yardımcı sınıfıdır. Örneğin, zaten bir erişim jetonunuz varsa aşağıdaki şekilde istekte bulunabilirsiniz:

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 kimliği

Bu alternatif kimlik bilgisi Google App Engine App Identity Java API'ye dayanır. İstemci uygulamasının son kullanıcının verilerine erişim isteğinde bulunduğu kimlik bilgisinden farklı olarak, App Identity API, istemci uygulamasının kendi verilerine erişim sağlar.

AppIdentityCredential'ı kullanın (google-api-client-appengine'den). Google App Engine tüm ayrıntıları hallettiği için bu kimlik bilgisi çok daha basittir. Yalnızca ihtiyacınız olan OAuth 2.0 kapsamını belirtirsiniz.

urlshortener-robots-appengine-sample adresinden alınan örnek kod:

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

Veri deposu

Bir erişim jetonunun genellikle son kullanma tarihi 1 saattir. Bu sürenin sonunda jetonu kullanmaya çalışırsanız hata alırsınız. GoogleCredential, jetonun otomatik olarak "yenilenmesini", yani yeni bir erişim jetonu almanızı sağlar. Bu işlem uzun süreli yenileme jetonuyla gerçekleştirilir. Bu jeton, yetkilendirme kodu akışı sırasında access_type=offline parametresini kullanırsanız genellikle erişim jetonuyla birlikte alınır (GoogleAuthorizationCodeFlow.Builder.setAccessType(String) bölümüne bakın).

Çoğu uygulamanın, kimlik bilgilerinin erişim jetonunu ve/veya yenileme jetonunu sürdürmesi gerekir. Kimlik bilgilerinin erişim ve/veya yenileme jetonlarını korumak için StoredCredential ile kendi DataStoreFactory uygulamanızı sağlayabilir veya kitaplık tarafından sağlanan aşağıdaki uygulamalardan birini kullanabilirsiniz:

AppEngine Kullanıcıları: AppEngineCredentialStore kullanımdan kaldırıldı ve yakında kaldırılacak. 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 MigrateTo(DataStore) yardımcı yöntemlerini kullanabilirsiniz.

DataStoreCredentialRefreshListener aracını kullanabilir ve GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) seçeneğini kullanarak kimlik bilgisi için bunu ayarlayabilirsiniz.)

Yetkilendirme kodu akışı

Son kullanıcının, Google API'lerindeki korunan verilerine uygulamanıza erişim izni vermesi için yetkilendirme kodu akışını kullanın. Bu akışın protokolü Yetkilendirme Kodu Verme bölümünde belirtilmiştir.

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

  • Son kullanıcı uygulamanıza giriş yapar. Bu kullanıcıyı uygulamanız için benzersiz bir kullanıcı kimliğiyle ilişkilendirmeniz gerekir.
  • Son kullanıcının kimlik bilgilerinin zaten bilinip bilinmediğini kontrol etmek için kullanıcı kimliğine göre AuthorizationCodeFlow.loadCredential(String)) yöntemini çağırın. Öyleyse işlemi tamamlamışızdır.
  • Aksi takdirde, AuthorizationCodeFlow.newAuthorizationUrl() işlevini çağırın ve uygulamanızın korunan verilere erişmesine izin vermek için son kullanıcının tarayıcısını bir yetkilendirme sayfasına yönlendirin.
  • Google yetkilendirme sunucusu, daha sonra tarayıcıyı code sorgu parametresiyle birlikte uygulamanız tarafından belirtilen yönlendirme URL'sine yönlendirir. AuthorizationCodeFlow.newTokenRequest(String) kullanarak erişim jetonu istemek için code parametresini kullanın.
  • Korunan kaynaklara erişmek için bir kimlik bilgisi depolamak ve kimlik bilgisi almak üzere AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String)) yöntemini kullanın.

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

Google API Konsolu'nda projenizi oluştururken, kullandığınız akışa bağlı olarak farklı kimlik bilgileri arasından seçim yaparsınız. Daha fazla bilgi için OAuth 2.0'ı Ayarlama ve OAuth 2.0 Senaryoları başlıklı makalelere göz atın. Her bir akışın kod snippet'leri aşağıda verilmiştir.

Web sunucusu uygulamaları

Bu akışın protokolü Web Sunucusu Uygulamaları için OAuth 2.0'ı Kullanma bölümünde açıklanmıştır.

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.

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 uygulamaları

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ştirilebilmesi için kullanıcının giriş yapması gerekir. Giriş yapmamışlarsa kullanıcıları bir giriş sayfasına yönlendirme hakkında bilgi için Güvenlik ve Kimlik Doğrulama (web.xml'de) bölümüne bakın.

Servlet durumundaki temel 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 (google-http-client-appengine'den), Google App Engine Data Store API'sini kullanarak kimlik bilgisini kalıcı tutmak için iyi bir seçenektir.

calendar-appengine-sample kaynağından alınmış örnek (biraz değiştirilmiş):

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

Ek bir örnek için storage-serviceaccount-appengine-sample adresine bakın.

Hizmet hesapları

GoogleCredential, hizmet hesaplarını da destekler. İstemci uygulamasının son kullanıcının verilerine erişim istediği kimlik bilgilerinin aksine, Hizmet Hesapları istemci uygulamasının kendi verilerine erişim sağlar. İstemci uygulamanız, Google API Konsolu'ndan indirilen bir özel anahtarı kullanarak erişim jetonu isteğini imzalar.

plus-serviceaccount-cmdline-sample öğesinden alınan örnek kod:

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

Ek bir örnek için storage-serviceaccount-cmdline-sample adresine bakın.

Kimliğe bürünme

Hizmet hesabı akışını, sahibi olduğunuz bir alandaki bir kullanıcının kimliğine bürünmek için de kullanabilirsiniz. Bu, yukarıdaki hizmet hesabı akışına çok benzer ancak ek olarak GoogleCredential.Builder.setServiceAccountUser(String) yöntemini çağırırsınız.

Yüklü uygulamalar

Bu, Yüklü Uygulamalarda OAuth 2.0'ı Kullanma bölümünde açıklanan komut satırı yetkilendirme kodu akışıdır.

plus-cmdline-sample kaynağından örnek snippet:

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

İstemci tarafı uygulamalar

İstemci Tarafı Uygulamalar için OAuth 2.0'ı Kullanma bölümünde açıklanan tarayıcı tabanlı istemci akışını kullanmak için genellikle aşağıdaki adımları uygulayın:

  1. Tarayıcı uygulamanızın, son kullanıcının korunan verilerine erişmesine izin vermek için tarayıcıdaki son kullanıcıyı GoogleBrowserClientRequestUrl kullanarak yetkilendirme sayfasına yönlendirin.
  2. Google API Konsolu'nda kayıtlı yönlendirme URI'sindeki URL parçasında bulunan erişim jetonunu işlemek için JavaScript için Google API İstemci Kitaplığı'nı kullanın.

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

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

@Beta

Android ile kullanılacak kitaplık:

Android için geliştiriyorsanız ve kullanmak istediğiniz Google API Google Play Hizmetleri kitaplığında yer alıyorsa en iyi performans ve deneyim için bu kitaplığı kullanın. Android ile kullanmak istediğiniz Google API'si, Google Play Hizmetleri kitaplığının bir parçası değilse Android 4.0 (Ice Cream Sandwich) (veya üzeri) sürümünü destekleyen Java için Google API İstemci Kitaplığı'nı kullanabilirsiniz. Bu kitaplık burada açıklanmaktadır. Java için Google API İstemci Kitaplığı'nda Android desteği @Beta şeklindedir.

Arka plan bilgileri:

Kullanıcı hesapları, Eclair'den (SDK 2.1) itibaren Android cihazlarda Hesap Yöneticisi kullanılarak yönetilir. Tüm Android uygulama yetkilendirmeleri, AccountManager kullanılarak SDK tarafından merkezi olarak yönetilir. Uygulamanızın ihtiyaç duyduğu OAuth 2.0 kapsamını belirtirsiniz ve bu kapsam, kullanılacak bir erişim jetonu döndürür.

OAuth 2.0 kapsamı, authTokenType parametresiyle oauth2: ve kapsam olarak belirtilir. Örneğin:

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

Bu, Google Tasks API'ye okuma/yazma erişimini belirtir. Birden fazla OAuth 2.0 kapsamına ihtiyacınız varsa boşlukla ayrılmış bir liste kullanın.

Bazı API'lerin özel authTokenType parametreleri de vardır. Örneğin, "Görevlerinizi yönetme", yukarıda gösterilen authtokenType örneğindeki takma addır.

API anahtarını Google API Konsolu'ndan da belirtmeniz gerekir. Aksi takdirde, Hesap Yöneticisi'nin size verdiği jeton, size yalnızca anonim kota sağlar. Bu da genellikle çok düşük bir kotadır. Buna karşın, bir API anahtarı belirterek daha yüksek bir ücretsiz kota elde edersiniz ve isteğe bağlı olarak bunun üzerindeki kullanım için faturalandırma ayarlayabilirsiniz.

tasks-android-sample öğesinden alınan örnek kod snippet'i:

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