Présentation
Objectif:ce document décrit les fonctions OAuth 2.0 génériques proposées par la bibliothèque cliente Google OAuth pour Java. Vous pouvez utiliser ces fonctions pour l'authentification et l'autorisation pour tous les services Internet.
Pour obtenir des instructions sur l'utilisation de GoogleCredential
pour autoriser l'authentification 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 permettant aux utilisateurs finaux d'autoriser de manière sécurisée une application cliente à accéder à des ressources côté serveur protégées. En outre, la spécification de 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 plus d'informations, consultez la documentation Javadoc pour les packages suivants:
- com.google.api.client.auth.oauth2 (à partir de google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (from google-oauth-client-servlet)
- com.google.api.client.extensions.appengine.auth.oauth2 (from google-oauth-client-appengine)
Enregistrement du client
Avant d'utiliser la bibliothèque cliente Google OAuth pour Java, vous devrez probablement enregistrer votre application auprès d'un serveur d'autorisation pour recevoir un ID et un code secret du client. Pour obtenir des informations générales sur ce processus, consultez la spécification d'enregistrement client.
Identifiants et stockage d'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'accès, Credential
actualise également le jeton d'accès à l'aide du jeton d'accès qui expire. Par exemple, si vous disposez déjà d'un jeton d'accès, vous pouvez effectuer 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 des identifiants afin d'éviter une redirection ultérieure vers la page d'autorisation dans le navigateur. L'intégration de CredentialStore dans cette bibliothèque est obsolète et sera supprimée dans les prochaines versions. L'alternative consiste à utiliser les interfaces DataStoreFactory et DataStore avec StoredCredential, qui sont fournies par la bibliothèque cliente HTTP Google pour Java.
Vous pouvez utiliser l'une des implémentations suivantes fournies par la bibliothèque:
- JdoDataStoreFactory conserve les identifiants à l'aide de Jenkins.
- 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 de Google App Engine:
AppEngineCredentialStore est obsolète et est en cours de suppression.
Nous vous recommandons d'utiliser AppEngineDataStoreFactory avec StoredCredential. Si vous avez stocké des identifiants de l'ancienne méthode, 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 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. Le protocole de ce flux est spécifié dans la spécification d'octroi de code d'autorisation.
Ce flux est mis en œuvre à l'aide d'AuthorizationCodeFlow. Voici la procédure à suivre :
- Un 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 ses identifiants sont déjà connus. Si c'est le cas, vous avez terminé.
- Si ce n'est pas le cas, appelez AuthorizationCodeFlow.newAuthorizationUrl() et dirigez le navigateur de l'utilisateur final vers une page d'autorisation sur laquelle il peut autoriser votre application à accéder à ses données protégées.
- Le navigateur Web redirige ensuite vers l'URL de redirection avec un paramètre de requête "code", qui peut ensuite être utilisé 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 AuthorizationCodeFlow, vous pouvez également utiliser les classes de niveau inférieur:
- Utilisez DataStore.get(String) pour charger les identifiants à partir du magasin, en fonction de l'ID utilisateur.
- Utilisez AuthorizationCodeRequestUrl pour rediriger le navigateur vers la page d'autorisation.
- Utilisez AuthorizationCodeResponseUrl pour traiter la réponse d'autorisation et analyser le code d'autorisation.
- Utilisez AuthorizationCodeTokenRequest pour demander un jeton d'accès et éventuellement un jeton d'actualisation.
- Créez un Credential (Identifiant) et stockez-le à l'aide de DataStore.set(String, V).
- Accédez aux ressources protégées à l'aide de l'identifiant. Les jetons d'accès expirés sont automatiquement actualisés à l'aide du jeton d'actualisation, le cas échéant. Veillez à utiliser DataStoreCredentialRefreshListener et à le définir pour l'identifiant à l'aide de Credential.Builder.addRefreshListener(CredentialRefreshListener).
Flux du code d'autorisation du servlet
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 AbstractAuthorizationCodeCallbackServlet (à partir de 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.
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 avec code d'autorisation 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 vers une page de connexion s'ils ne sont pas déjà connectés, consultez la page 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 AbstractAppEngineAuthorizationCodeCallbackServlet (à partir de google-oauth-client-appengine). Ils étendent les classes de servlets abstraites et implémentent la méthode getUserId
à votre place à l'aide de l'API Java Users. AppEngineDataStoreFactory (de la bibliothèque cliente HTTP Google pour Java) est une bonne option pour conserver l'identifiant à 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 avec code d'autorisation sur la ligne de commande
Exemple de code simplifié tiré 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 flux client basé sur un navigateur, lesquelles sont spécifiées dans la spécification d'Implicit Grant:
- À l'aide de BrowserClientRequestUrl, redirigez le navigateur de l'utilisateur final vers la page d'autorisation sur laquelle celui-ci peut accorder à votre application l'accès à 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é 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 expiré
Conformément à 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
tel que le suivant:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
Cependant, la spécification semble beaucoup 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. Toutefois, le jeton d'accès peut ne pas expirer à la fin de cette période, et le serveur peut continuer à autoriser l'accès. C'est pourquoi nous vous recommandons généralement d'attendre un code d'état 401 Unauthorized
, plutôt que de supposer que le jeton a expiré en fonction du temps écoulé. Vous pouvez également essayer d'actualiser un jeton d'accès peu de temps avant son expiration. Si le serveur de jetons est indisponible, continuez à l'utiliser jusqu'à ce que vous receviez un 401
. Il s'agit de la stratégie utilisée par défaut dans 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 à envoyer au serveur de jetons à 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 persistant et sécurisé afin de réduire le nombre de demandes de nouveaux jetons d'une application. Toutefois, 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 d'autres raisons que l'expiration, par exemple si l'utilisateur a explicitement révoqué le jeton. Assurez-vous donc que votre code de gestion des erreurs est robuste. 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.