Panoramica
Scopo: questo documento descrive le funzioni OAuth 2.0 generiche offerte dalla libreria client OAuth di Google per Java. Puoi utilizzare queste funzioni per l'autenticazione e l'autorizzazione per qualsiasi servizio internet.
Per istruzioni su come utilizzare GoogleCredential
per eseguire l'autorizzazione OAuth 2.0 con i servizi Google, consulta Utilizzare OAuth 2.0 con la libreria client dell'API Google per Java.
Riepilogo: OAuth 2.0 è una specifica standard che consente agli utenti finali di autorizzare in sicurezza un'applicazione client ad accedere a risorse lato server protette. Inoltre, la specifica del token bearer OAuth 2.0 spiega come accedere a queste risorse protette utilizzando un token di accesso concesso durante la procedura di autorizzazione dell'utente finale.
Per maggiori dettagli, consulta la documentazione Javadoc per i seguenti pacchetti:
- com.google.api.client.auth.oauth2 (da google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (da google-oauth-client-servlet)
- com.google.api.client.extensions.appengine.auth.oauth2 (from google-oauth-client-appengine)
Registrazione del cliente
Prima di utilizzare la libreria client OAuth di Google per Java, probabilmente dovrai registrare la tua applicazione con un server di autorizzazione per ricevere un ID client e un client secret. Per informazioni generali su questa procedura, consulta la specifica relativa alla registrazione client.
Credenziali e archivio delle credenziali
Credential
è una classe di assistenza OAuth 2.0 sicura per i thread per accedere alle risorse protette utilizzando un
token di accesso. Se utilizzi un token di aggiornamento, Credential
aggiorna anche il token
di accesso quando scade utilizzando il token di aggiornamento. Ad esempio, se hai già un token di accesso, puoi effettuare una richiesta nel seguente modo:
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 maggior parte delle applicazioni deve mantenere il token di accesso e il token di aggiornamento della credenziale per evitare un futuro reindirizzamento alla pagina di autorizzazione nel browser. L'implementazione di CredentialStore in questa libreria è deprecata e verrà rimossa nelle release future. L'alternativa è utilizzare le interfacce DataStoreFactory e DataStore con StoredCredential, fornite dalla libreria client HTTP di Google per Java.
Puoi utilizzare una delle seguenti implementazioni fornite dalla libreria:
- JdoDataStoreFactory mantiene la credenziale utilizzando JDO.
- AppEngineDataStoreFactory mantiene la credenziale utilizzando l'API Google App Engine Data Store.
- MemoryDataStoreFactory "mantiene" la credenziale in memoria, che è utile solo come archiviazione breve per la durata del processo.
- FileDataStoreFactory mantiene la credenziale in un file.
Utenti di Google App Engine:
AppEngineCredentialStore è deprecato e verrà rimosso.
Ti consigliamo di utilizzare AppEngineDataStoreFactory con StoredCredential. Se hai credenziali archiviate nel vecchio modo, puoi utilizzare i metodi di assistenza aggiunti migrateTo(AppEngineDataStoreFactory) o migrateTo(DataStore) per eseguire la migrazione.
Utilizza DataStoreCredentialRefreshListener e impostalo per la credenziale utilizzando GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).
Flusso del codice di autorizzazione
Utilizza il flusso del codice di autorizzazione per consentire all'utente finale di concedere alla tua applicazione accesso ai suoi dati protetti. Il protocollo per questo flusso è specificato nella specifica della concessione del codice di autorizzazione.
Questo flusso viene implementato utilizzando AuthorizationCodeFlow. I passaggi sono:
- Un utente finale accede alla tua applicazione. Devi associare l'utente a un ID utente univoco per la tua applicazione.
- Chiama AuthorizationCodeFlow.loadCredential(String), in base all'ID utente, per verificare se le credenziali dell'utente sono già note. Se è così, non devi fare altro.
- In caso contrario, chiama AuthorizationCodeFlow.newAuthorizationUrl() e indirizza il browser dell'utente finale a una pagina di autorizzazione in cui può concedere alla tua applicazione l'accesso ai dati protetti.
- Il browser web quindi reindirizza all'URL di reindirizzamento con un parametro di query "code" che può essere utilizzato per richiedere un token di accesso utilizzando AuthorizationCodeFlow.newTokenRequest(String).
- Utilizza AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) per archiviare e ottenere una credenziale per accedere alle risorse protette.
In alternativa, se non utilizzi AuthorizationCodeFlow, puoi utilizzare le classi di livello inferiore:
- Utilizza DataStore.get(String) per caricare la credenziale dal negozio in base all'ID utente.
- Utilizza AuthorizationCodeRequestUrl per indirizzare il browser alla pagina di autorizzazione.
- Utilizza AuthorizationCodeResponseUrl per elaborare la risposta di autorizzazione e analizzare il codice di autorizzazione.
- Utilizza AuthorizationCodeTokenRequest per richiedere un token di accesso e, eventualmente, un token di aggiornamento.
- Crea una nuova credenziale e memorizzala utilizzando DataStore.set(String, V).
- Accedi alle risorse protette utilizzando la credenziale. I token di accesso scaduti vengono aggiornati automaticamente utilizzando il token di aggiornamento, se applicabile. Assicurati di utilizzare DataStoreCredentialRefreshListener e impostalo per la credenziale utilizzando Credential.Builder.addRefreshListener(CredentialRefreshListener).
Flusso del codice di autorizzazione del servlet
Questa libreria fornisce classi helper servlet per semplificare notevolmente il flusso del codice di autorizzazione per i casi d'uso di base. Devi solo fornire sottoclassi concrete di AbstractAuthorizationCodeServlet e AbstractAuthorizationCodeCallbackServlet (da google-oauth-client-servlet) e aggiungerle al file web.xml. Tieni presente che devi comunque occuparti dell'accesso degli utenti alla tua applicazione web ed estrarre un ID utente.
Codice di esempio:
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 } }
Flusso del codice di autorizzazione di Google App Engine
Il flusso del codice di autorizzazione su App Engine è quasi identico al flusso del codice di autorizzazione servlet, ma possiamo sfruttare l'API Users Java di Google App Engine. L'utente deve aver eseguito l'accesso affinché l'API Java Users venga attivata. Per informazioni su come reindirizzare gli utenti a una pagina di accesso se non hanno ancora eseguito l'accesso, consulta Sicurezza e autenticazione (in web.xml).
La differenza principale rispetto al caso del servlet è che fornisci sottoclassi concrete di AbstractAppEngineAuthorizationCodeServlet e AbstractAppEngineAuthorizationCodeCallbackServlet (da google-oauth-client-appengine). Estendono le classi servlet astratte e implementano il metodo getUserId
per te utilizzando l'API Java Users. AppEngineDataStoreFactory (della libreria client HTTP di Google per Java) è una buona opzione per la persistenza della credenziale utilizzando l'API Google App Engine Data Store.
Codice di esempio:
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(); } }
Flusso del codice di autorizzazione dalla riga di comando
Codice di esempio semplificato tratto da 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); ... }
Flusso di clienti basato su browser
Di seguito sono riportati i passaggi tipici del flusso del client basato su browser specificato nella specifica della concessione implicita:
- Utilizzando BrowserClientRequestUrl, reindirizza il browser dell'utente finale alla pagina di autorizzazione in cui l'utente finale può concedere alla tua applicazione l'accesso ai suoi dati protetti.
- Utilizza un'applicazione JavaScript per elaborare il token di accesso trovato nel frammento dell'URL nell'URI di reindirizzamento registrato con il server di autorizzazione.
Esempio di utilizzo per un'applicazione 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); }
Rilevamento di un token di accesso scaduto
In base alla specifica del token bearer OAuth 2.0,
quando il server viene chiamato per accedere a una risorsa protetta con un token di accesso scadeto, tipicamente risponde con un codice di stato HTTP 401 Unauthorized
, come il seguente:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
Tuttavia, sembra esserci molta flessibilità nella specifica. Per maggiori dettagli, consulta la documentazione del provider OAuth 2.0.
Un approccio alternativo è controllare il parametro expires_in
nella
risposta del token di accesso.
Specifica la durata in secondi del token di accesso concesso, che in genere è di un'ora. Tuttavia, il token di accesso potrebbe non scadere effettivamente alla fine di questo periodo e il server potrebbe continuare a consentire l'accesso. Per questo motivo, solitamente consigliamo di attendere un codice di stato 401 Unauthorized
anziché presumere che il token sia scaduto in base al tempo trascorso. In alternativa, puoi provare ad aggiornare un token di accesso poco prima della scadenza e, se il server dei token non è disponibile, continuare a utilizzare il token di accesso finché non ricevi un 401
. Questa è la strategia utilizzata per impostazione predefinita in Credential.
Un'altra opzione è quella di recuperare un nuovo token di accesso prima di ogni richiesta, ma ciò richiede ogni volta una richiesta HTTP aggiuntiva al server token, quindi è probabilmente una scelta scarsa in termini di velocità e utilizzo della rete. Idealmente, memorizza il token di accesso in uno spazio di archiviazione sicuro e permanente per ridurre al minimo le richieste di nuovi token di accesso da parte di un'applicazione. Tuttavia, per le applicazioni installate, lo spazio di archiviazione sicuro è un problema difficile.
Tieni presente che un token di accesso potrebbe non essere valido per motivi diversi dalla scadenza, ad esempio se l'utente ha revocato esplicitamente il token, quindi assicurati che il codice di gestione degli errori sia affidabile. Una volta rilevato che un token non è più valido, ad esempio se è scaduto o è stato revocato, devi rimuovere il token di accesso dallo spazio di archiviazione. Ad esempio, su Android devi chiamare AccountManager.invalidateAuthToken.