Utiliser OAuth 2.0 avec la bibliothèque cliente des API Google pour Java

Présentation

Objectif:Ce document explique comment utiliser la classe utilitaire GoogleCredential pour effectuer l'autorisation OAuth 2.0 avec les services Google. Pour en savoir plus sur les fonctions OAuth 2.0 génériques que nous fournissons, consultez la page OAuth 2.0 et la bibliothèque cliente Google OAuth pour Java.

Résumé:Pour accéder aux données protégées stockées sur les services Google, utilisez OAuth 2.0 pour l'autorisation. Les API Google sont compatibles avec les flux OAuth 2.0 pour différents types d'applications clientes. Dans tous ces flux, l'application cliente demande un jeton d'accès associé uniquement à votre application cliente et au propriétaire des données protégées faisant l'objet d'un accès. Le jeton d'accès est également associé à un champ d'application limité qui définit le type de données auquel votre application cliente a accès (par exemple, "Gérer vos tâches"). L'un des objectifs importants d'OAuth 2.0 est de fournir un accès sécurisé et pratique aux données protégées, tout en réduisant au maximum l'impact potentiel en cas de vol d'un jeton d'accès.

Les packages OAuth 2.0 de la bibliothèque cliente des API Google pour Java sont basés sur la bibliothèque cliente Google OAuth 2.0 pour Java à usage général.

Pour plus d'informations, consultez la documentation Javadoc pour les packages suivants:

Console Google APIs

Avant de pouvoir accéder aux API Google, vous devez configurer un projet dans la console Google APIs à des fins d'authentification et de facturation, que votre client soit une application installée, une application mobile, un serveur Web ou un client qui s'exécute dans un navigateur.

Pour savoir comment configurer correctement vos identifiants, consultez l'aide de la console API.

Identifiant

GoogleCredential

GoogleCredential est une classe d'assistance thread-safe pour OAuth 2.0 qui permet d'accéder à des ressources protégées à l'aide d'un jeton d'accès. Par exemple, si vous disposez déjà d'un jeton d'accès, vous pouvez effectuer une requête de la manière suivante:

GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Plus plus = new Plus.builder(new NetHttpTransport(),
                             GsonFactory.getDefaultInstance(),
                             credential)
    .setApplicationName("Google-PlusSample/1.0")
    .build();

Identité Google App Engine

Cet autre identifiant est basé sur l'API App Identity Java de Google App Engine. Contrairement aux identifiants via lesquels une application cliente demande l'accès aux données d'un utilisateur final, l'API App Identity fournit un accès aux données de l'application cliente.

Utilisez AppIdentityCredential (à partir de google-api-client-appengine). Cet identifiant est beaucoup plus simple, car Google App Engine se charge de tous les détails. Vous spécifiez uniquement le champ d'application OAuth 2.0 dont vous avez besoin.

Exemple de code issu de 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();
}

Datastore

Un jeton d'accès a généralement une date d'expiration d'une heure. Une erreur s'affiche ensuite si vous essayez de l'utiliser. GoogleCredential se charge de l'"actualisation" automatique du jeton, ce qui signifie simplement de l'obtention d'un nouveau jeton d'accès. Pour ce faire, vous devez utiliser un jeton d'actualisation de longue durée, qui est généralement reçu avec le jeton d'accès si vous utilisez le paramètre access_type=offline pendant le flux de code d'autorisation (voir GoogleAuthorizationCodeFlow.Builder.setAccessType(String)).

La plupart des applications devront conserver le jeton d'accès et/ou le jeton d'actualisation des identifiants. Pour conserver les jetons d'accès et/ou d'actualisation des identifiants, vous pouvez fournir votre propre implémentation de DataStoreFactory avec StoredCredential, ou utiliser l'une des implémentations suivantes fournies par la bibliothèque:

  • AppEngineDataStoreFactory : conserve les identifiants à l'aide de l'API Google App Engine Data Store.
  • MemoryDataStoreFactory : "conserve" les identifiants en mémoire, qui n'est utile qu'en tant que stockage à court terme pendant toute la durée de vie du processus.
  • FileDataStoreFactory : conserve les identifiants dans un fichier.

Utilisateurs d'App Engine:AppEngineCredentialStore est obsolète et sera bientôt supprimé. Nous vous recommandons d'utiliser AppEngineDataStoreFactory avec StoredCredential. Si vous avez stocké des identifiants à l'ancienne, vous pouvez utiliser les méthodes d'assistance ajoutées migrateTo(AppEngineDataStoreFactory) ou migrateTo(DataStore) pour effectuer la migration.

Vous pouvez utiliser DataStoreCredentialRefreshListener et le définir pour l'identifiant à l'aide de GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener)).

Flux avec le code d'autorisation

Utilisez le flux avec code d'autorisation pour permettre à l'utilisateur final d'autoriser votre application à accéder à ses données protégées sur les API Google. Le protocole de ce flux est spécifié dans Attribution du code d'autorisation.

Ce flux est implémenté à l'aide de GoogleAuthorizationCodeFlow. Voici la procédure à suivre :

  • L'utilisateur final se connecte à votre application. Vous devez associer cet utilisateur à un ID utilisateur propre à votre application.
  • Appelez AuthorizationCodeFlow.loadCredential(String)) en fonction de l'ID utilisateur pour vérifier si les identifiants de l'utilisateur final sont déjà connus. Si c'est le cas, nous avons terminé.
  • Si ce n'est pas le cas, appelez AuthorizationCodeFlow.newAuthorizationUrl() et dirigez le navigateur de l'utilisateur final vers une page d'autorisation pour permettre à votre application d'accéder à ses données protégées.
  • Le serveur d'autorisation Google redirige ensuite le navigateur vers l'URL de redirection spécifiée par votre application, avec un paramètre de requête code. Utilisez le paramètre code pour demander un jeton d'accès à l'aide de AuthorizationCodeFlow.newTokenRequest(String)).
  • Utilisez AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String)) pour stocker et obtenir un identifiant pour accéder aux ressources protégées.

Si vous n'utilisez pas GoogleAuthorizationCodeFlow, vous pouvez également utiliser les classes de niveau inférieur:

Lorsque vous configurez votre projet dans la console Google APIs, vous sélectionnez différents identifiants en fonction du flux que vous utilisez. Pour en savoir plus, consultez Configurer OAuth 2.0 et Scénarios OAuth 2.0. Vous trouverez ci-dessous des extraits de code pour chacun des flux.

Applications de serveur Web

Le protocole de ce flux est expliqué dans la page Utiliser OAuth 2.0 pour les applications de serveur Web.

Cette bibliothèque fournit des classes d'assistance de servlet qui simplifient considérablement le flux de code d'autorisation pour les cas d'utilisation de base. Il vous suffit de fournir les sous-classes concrètes de AbstractAuthorizationCodeServlet et de AbstractAuthorizationCodeCallbackServlet (à partir du fichier google-oauth-client-servlet), et de les ajouter à votre fichier web.xml. Notez que vous devez toujours vous occuper de la connexion utilisateur pour votre application Web et extraire un ID utilisateur.

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

Applications Google App Engine

Le flux de code d'autorisation sur App Engine est presque identique au flux de code d'autorisation des servlets, sauf que nous pouvons exploiter l'API Users Java de Google App Engine. L'utilisateur doit être connecté pour activer l'API Java Users. Pour savoir comment rediriger les utilisateurs qui ne sont pas déjà connectés vers une page de connexion, consultez Sécurité et authentification (dans web.xml).

La principale différence par rapport au cas du servlet est que vous fournissez des sous-classes concrètes de AbstractAppEngineAuthorizationCodeServlet et de AbstractAppEngineAuthorizationCodeCallbackServlet (à partir de google-oauth-client-appengine. Ils étendent les classes de servlets abstraites et implémentent la méthode getUserId pour vous à l'aide de l'API Java Users. AppEngineDataStoreFactory (disponible sur google-http-client-appengine) est une bonne option pour conserver les identifiants à l'aide de l'API Data Store de Google App Engine.

Exemple (légèrement modifié) tiré de 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();
  }
}

Pour obtenir un exemple supplémentaire, consultez la page storage-serviceaccount-appengine-sample.

Comptes de service

GoogleCredential est également compatible avec les comptes de service. Contrairement aux identifiants via lesquels une application cliente demande l'accès aux données d'un utilisateur final, les comptes de service fournissent un accès aux propres données de l'application cliente. Votre application cliente signe la requête de jeton d'accès à l'aide d'une clé privée téléchargée à partir de la console Google APIs.

Exemple de code issu de 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();
...

Pour obtenir un autre exemple, consultez la page storage-serviceaccount-cmdline-sample.

Usurpation d'identité

Vous pouvez également utiliser la procédure de compte de service pour emprunter l'identité d'un utilisateur d'un domaine qui vous appartient. Cette procédure est très semblable au flux de compte de service ci-dessus, mais vous appelez également GoogleCredential.Builder.setServiceAccountUser(String).

Applications installées

Il s'agit du flux avec code d'autorisation en ligne de commande décrit dans l'article Utiliser OAuth 2.0 pour les applications installées.

Exemple d'extrait de code à partir de 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");
}

Applications côté client

Pour utiliser le flux client basé sur un navigateur décrit dans la section Utiliser OAuth 2.0 pour les applications côté client, procédez comme suit:

  1. Dans le navigateur, redirigez l'utilisateur final vers la page d'autorisation à l'aide de GoogleBrowserClientRequestUrl pour autoriser votre application de navigateur à accéder aux données protégées de l'utilisateur final.
  2. Utilisez la bibliothèque cliente des API Google pour JavaScript pour traiter le jeton d'accès trouvé dans le fragment d'URL au niveau de l'URI de redirection enregistré dans la console Google APIs.

Exemple d'utilisation pour une application Web:

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

@Bêta

Quelle bibliothèque utiliser avec Android ?

Si vous développez pour Android et que l'API Google que vous souhaitez utiliser est incluse dans la bibliothèque des services Google Play, utilisez-la pour optimiser les performances et l'expérience. Si l'API Google que vous souhaitez utiliser avec Android ne fait pas partie de la bibliothèque des services Google Play, vous pouvez utiliser la bibliothèque cliente des API Google pour Java, qui est compatible avec Android 4.0 (Ice Cream Sandwich) (ou version ultérieure) et qui est décrite ici. La compatibilité d'Android dans la bibliothèque cliente des API Google pour Java est @Beta.

Contexte :

À partir d'Eclair (SDK 2.1), les comptes utilisateur sont gérés sur un appareil Android à l'aide du gestionnaire de comptes. Toutes les autorisations des applications Android sont gérées de manière centralisée par le SDK à l'aide de AccountManager. Vous spécifiez le champ d'application OAuth 2.0 dont votre application a besoin et renvoie un jeton d'accès à utiliser.

Le champ d'application d'OAuth 2.0 est spécifié via le paramètre authTokenType en ajoutant oauth2: plus le champ d'application. Exemple :

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

Ce paramètre spécifie un accès en lecture/écriture à l'API Google Tasks. Si vous avez besoin de plusieurs champs d'application OAuth 2.0, séparez-les par des espaces.

Certaines API possèdent des paramètres authTokenType spéciaux qui fonctionnent également. Par exemple, "Gérer vos tâches" est un alias pour l'exemple authtokenType présenté ci-dessus.

Vous devez également spécifier la clé API à partir de la console Google APIs. Sinon, le jeton fourni par AccountManager ne vous fournit qu'un quota anonyme, qui est généralement très faible. En revanche, spécifier une clé API permet de bénéficier d'un quota sans frais plus élevé et peut éventuellement configurer la facturation pour une utilisation supérieure à ce quota.

Exemple d'extrait de code tiré de 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;
  }
}