OAuth 2.0 et la bibliothèque cliente Google OAuth pour Java

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Présentation

Objectif : Ce document décrit les fonctions génériques OAuth 2.0 proposées par la bibliothèque cliente Google OAuth pour Java. Vous pouvez utiliser ces fonctions pour l'authentification et l'autorisation de n'importe quel service Internet.

Pour obtenir des instructions sur l'utilisation de GoogleCredential afin d'effectuer une autorisation OAuth 2.0 avec les services Google, consultez la page Utiliser OAuth 2.0 avec la bibliothèque cliente des API Google pour Java.

Résumé : OAuth 2.0 est une spécification standard qui permet aux utilisateurs finaux d'autoriser en toute sécurité une application cliente à accéder à des ressources protégées côté serveur. En outre, la spécification du jeton de support OAuth 2.0 explique comment accéder à ces ressources protégées à l'aide d'un jeton d'accès accordé lors du processus d'autorisation de l'utilisateur final.

Pour en savoir plus, consultez la documentation Javadoc sur les packages suivants:

Enregistrement du client

Avant d'utiliser la bibliothèque cliente Google OAuth pour Java, vous devez probablement enregistrer votre application auprès d'un serveur d'autorisation pour recevoir un ID client et un code secret du client. (Pour obtenir des informations générales sur ce processus, consultez la spécification concernant l'enregistrement client.)

Identifiants et stockage des identifiants

Credential est une classe d'assistance OAuth 2.0 sécurisée pour l'accès aux ressources protégées à l'aide d'un jeton d'accès. Lorsque vous utilisez un jeton d'actualisation, Credential actualise également le jeton d'accès lorsque le jeton d'accès expire à l'aide du jeton d'actualisation. Par exemple, si vous disposez déjà d'un jeton d'accès, vous pouvez envoyer une requête de la manière suivante:

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

La plupart des applications doivent conserver le jeton d'accès et le jeton d'actualisation afin d'éviter toute redirection ultérieure vers la page d'autorisation du navigateur. L'implémentation de CredentialStore dans cette bibliothèque est obsolète et sera supprimée dans les prochaines versions. Vous pouvez également utiliser les interfaces DataStoreFactory et DataStore avec StoredCredential, qui est fourni par la bibliothèque cliente HTTP Google pour Java.

Vous pouvez utiliser l'une des implémentations suivantes fournies par la bibliothèque:

Utilisateurs de Google App Engine:

AppEngineCredentialStore est obsolète et va être supprimé.

Nous vous recommandons d'utiliser AppEngineDataStoreFactory avec StoredCredential. Si vos anciens identifiants sont stockés, vous pouvez utiliser les méthodes d'assistance ajoutées migrateTo(AppEngineDataStoreFactory) ou migrateTo(DataStore) pour la migration.

Utilisez DataStoreCredentialRefreshListener et définissez-le pour les identifiants à l'aide de GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).

Flux de code d'autorisation

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

Ce flux est mis en œuvre à l'aide de AuthorizationCodeFlow. Voici la procédure à suivre :

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

Flux de code d'autorisation du jeton

Cette bibliothèque fournit des classes auxiliaires pour simplifier le flux de code d'autorisation dans les cas d'utilisation de base. Il vous suffit de fournir des sous-classes concrètes de abstractionAuthorizationCodeServlet et abstractionAuthorizationCodeCallbackServlet (depuis google-oauth-client-vault), puis de les ajouter à votre fichier web.xml. Notez que vous devez toujours vous charger de la connexion des utilisateurs pour votre application Web et extraire un ID utilisateur.

Exemple de code :

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

Flux de code d'autorisation Google App Engine

Le flux de code d'autorisation sur App Engine est presque identique au flux de code d'autorisation du webhook, sauf que nous pouvons exploiter l'API Users pour Java de Google App Engine. L'utilisateur doit être connecté pour que l'API Users pour Java soit activée. Pour plus d'informations sur la redirection des utilisateurs vers une page de connexion s'ils ne sont pas déjà connectés, consultez la section Sécurité et authentification (dans web.xml).

La principale différence par rapport au cas du microservice est que vous fournissez des sous-classes concrètes de abstractionAppEngineAuthorizationCodeServlet et abstractionAppEngineAuthorizationCodeCallbackServlet (depuis google-oauth-client-appengine). Ils étendent les classes de webhook abstraites et implémentent la méthode getUserId pour vous à l'aide de l'API Users pour Java. AppEngineDataStoreFactory (issu de la bibliothèque cliente HTTP Google pour Java est une bonne option pour conserver les identifiants à l'aide de l'API Google App Engine Data Store.

Exemple de code :

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

Flux de code d'autorisation via la ligne de commande

Exemple de code simplifié issu de 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);
  ...
}

Flux client basé sur un navigateur

Voici les étapes typiques du parcours client basées sur un navigateur spécifiées dans la spécification implicite d'attribution:

  • À l'aide de BrowserClientRequestUrl, redirigez le navigateur de l'utilisateur final vers la page d'autorisation sur laquelle l'utilisateur final peut autoriser votre application à accéder à ses données protégées.
  • Utilisez une application JavaScript pour traiter le jeton d'accès trouvé dans le fragment d'URL au niveau de l'URI de redirection enregistrée auprès du serveur d'autorisation.

Exemple d'utilisation pour une application Web:

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

Détecter un jeton d'accès arrivé à expiration

Selon la spécification du support OAuth 2.0, lorsque le serveur est appelé pour accéder à une ressource protégée avec un jeton d'accès arrivé à expiration, il répond généralement par un code d'état HTTP 401 Unauthorized comme celui-ci:

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

Cependant, la spécification semble être extrêmement flexible. Pour en savoir plus, consultez la documentation du fournisseur OAuth 2.0.

Une autre approche consiste à vérifier le paramètre expires_in dans la réponse du jeton d'accès. Ce champ indique la durée de vie, en secondes, du jeton d'accès accordé, qui est généralement d'une heure. Cependant, il se peut que le jeton d'accès n'expire pas à la fin de cette période et que le serveur continue à autoriser l'accès. C'est pourquoi nous recommandons généralement d'attendre un code d'état 401 Unauthorized au lieu de supposer que le jeton a expiré en fonction du temps écoulé. Vous pouvez également essayer d'actualiser un jeton d'accès peu avant son expiration. Si le serveur de jetons n'est pas disponible, continuez à utiliser le jeton d'accès jusqu'à ce que vous receviez un 401. Il s'agit de la stratégie utilisée par défaut dans la section Identifiants.

Une autre option consiste à récupérer un nouveau jeton d'accès avant chaque requête, mais cela nécessite une requête HTTP supplémentaire à chaque fois. Il s'agit donc probablement d'un mauvais choix en termes de vitesse et d'utilisation du réseau. Idéalement, stockez le jeton d'accès dans un espace de stockage sécurisé et persistant pour minimiser les requêtes d'une application concernant les nouveaux jetons d'accès. (mais pour les applications installées, le stockage sécurisé est un problème difficile).

Notez qu'un jeton d'accès peut devenir non valide pour des raisons autres que l'expiration, par exemple si l'utilisateur a explicitement révoqué le jeton. Assurez-vous donc que votre code de traitement des erreurs est fiable. Une fois que vous avez détecté qu'un jeton n'est plus valide, par exemple s'il a expiré ou a été révoqué, vous devez le supprimer de votre espace de stockage. Sur Android, par exemple, vous devez appeler AccountManager.invalidateAuthToken.