Gestore callback autorizzazione build

Questo documento spiega come implementare un gestore del callback di autorizzazione OAuth 2.0 utilizzando servlet Java tramite un'applicazione web di esempio che mostra le attività dell'utente utilizzando l'API Google Tasks. L'applicazione di esempio richiederà innanzitutto l'autorizzazione ad accedere alle attività di Google Tasks dell'utente, quindi mostrerà le attività dell'utente nell'elenco delle attività predefinite.

Pubblico

Questo documento è stato creato per utenti che hanno familiarità con l'architettura di applicazioni web Java e J2EE. È consigliabile avere una certa conoscenza del flusso di autorizzazione OAuth 2.0.

Sommario

Per ottenere questo esempio completamente funzionante, sono necessari diversi passaggi, devi:

Dichiara le mappature servlet nel file web.xml

Utilizzeremo due servlet nella nostra applicazione:

  • PrintTasksTitlesServlet (mappato a /): il punto di ingresso dell'applicazione che gestirà l'autenticazione dell'utente e mostrerà le attività dell'utente.
  • OAuthCodeCallbackHandlerServlet (mappato a /oauth2callback): il callback OAuth 2.0 che gestisce la risposta dall'endpoint di autorizzazione OAuth

Di seguito è riportato il file web.xml che mappa questi 2 servlet agli URL nella nostra applicazione:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

 <servlet>
   <servlet-name>PrintTasksTitles</servlet-name>
   <servlet-class>com.google.oauthsample.PrintTasksTitlesServlet</servlet-class>
 </servlet>

 <servlet-mapping>
   <servlet-name>PrintTasksTitles</servlet-name>
   <url-pattern>/</url-pattern>
 </servlet-mapping>

 <servlet>
   <servlet-name>OAuthCodeCallbackHandlerServlet</servlet-name>
   <servlet-class>com.google.oauthsample.OAuthCodeCallbackHandlerServlet</servlet-class>
 </servlet>

 <servlet-mapping>
   <servlet-name>OAuthCodeCallbackHandlerServlet</servlet-name>
   <url-pattern>/oauth2callback</url-pattern>
 </servlet-mapping>

</web-app>
/WEB-INF/web.xml File

Autentica gli utenti sul tuo sistema e richiedi l'autorizzazione ad accedere alle sue attività

L'utente inserisce l'applicazione tramite l'URL principale "/" mappato al servlet PrintTaskListsTitlesServlet. In questo servlet vengono eseguite le seguenti attività:

  • Verifica se l'utente è autenticato nel sistema
  • Se l'utente non è autenticato, viene reindirizzato alla pagina di autenticazione.
  • Se l'utente è autenticato, verifichiamo se nel nostro spazio di archiviazione dei dati è già presente un token di aggiornamento, che viene gestito dall'OAuthTokenDao di seguito. Se per l'utente non sono disponibili token di aggiornamento, significa che l'utente non ha ancora concesso all'applicazione l'autorizzazione ad accedere alle sue attività. In questo caso l'utente viene reindirizzato all'endpoint di autorizzazione OAuth 2.0 di Google.
Di seguito è riportato un modo per implementare questa funzionalità:

package com.google.oauthsample;

import ...

/**
 * Simple sample Servlet which will display the tasks in the default task list of the user.
 */
@SuppressWarnings("serial")
public class PrintTasksTitlesServlet extends HttpServlet {

  /**
   * The OAuth Token DAO implementation, used to persist the OAuth refresh token.
   * Consider injecting it instead of using a static initialization. Also we are
   * using a simple memory implementation as a mock. Change the implementation to
   * using your database system.
   */
  public static OAuthTokenDao oauthTokenDao = new OAuthTokenDaoMemoryImpl();

  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // Getting the current user
    // This is using App Engine's User Service but you should replace this to
    // your own user/login implementation
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();

    // If the user is not logged-in it is redirected to the login service, then back to this page
    if (user == null) {
      resp.sendRedirect(userService.createLoginURL(getFullRequestUrl(req)));
      return;
    }

    // Checking if we already have tokens for this user in store
    AccessTokenResponse accessTokenResponse = oauthTokenDao.getKeys(user.getEmail());

    // If we don't have tokens for this user
    if (accessTokenResponse == null) {
      OAuthProperties oauthProperties = new OAuthProperties();
      // Redirect to the Google OAuth 2.0 authorization endpoint
      resp.sendRedirect(new GoogleAuthorizationRequestUrl(oauthProperties.getClientId(),
          OAuthCodeCallbackHandlerServlet.getOAuthCodeCallbackHandlerUrl(req), oauthProperties
              .getScopesAsString()).build());
      return;
    }
  }

  /**
   * Construct the request's URL without the parameter part.
   *
   * @param req the HttpRequest object
   * @return The constructed request's URL
   */
  public static String getFullRequestUrl(HttpServletRequest req) {
    String scheme = req.getScheme() + "://";
    String serverName = req.getServerName();
    String serverPort = (req.getServerPort() == 80) ? "" : ":" + req.getServerPort();
    String contextPath = req.getContextPath();
    String servletPath = req.getServletPath();
    String pathInfo = (req.getPathInfo() == null) ? "" : req.getPathInfo();
    String queryString = (req.getQueryString() == null) ? "" : "?" + req.getQueryString();
    return scheme + serverName + serverPort + contextPath + servletPath + pathInfo + queryString;
  }
}
File PrintTasksTitlesServlet.java

Nota: l'implementazione descritta sopra utilizza alcune librerie di App Engine, che vengono utilizzate per semplificare l'implementazione. Se stai sviluppando per un'altra piattaforma, puoi implementare nuovamente l'interfaccia UserService che gestisce l'autenticazione utente.

L'applicazione utilizza un DAO per persistere e accedere ai token di autorizzazione dell'utente. Di seguito sono riportate l'interfaccia OAuthTokenDao e un'implementazione fittizia (in memoria) OAuthTokenDaoMemoryImpl che vengono utilizzate in questo esempio:

package com.google.oauthsample;

import com.google.api.client.auth.oauth2.draft10.AccessTokenResponse;

/**
 * Allows easy storage and access of authorization tokens.
 */
public interface OAuthTokenDao {

  /**
   * Stores the given AccessTokenResponse using the {@code username}, the OAuth
   * {@code clientID} and the tokens scopes as keys.
   *
   * @param tokens The AccessTokenResponse to store
   * @param userName The userName associated wit the token
   */
  public void saveKeys(AccessTokenResponse tokens, String userName);

  /**
   * Returns the AccessTokenResponse stored for the given username, clientId and
   * scopes. Returns {@code null} if there is no AccessTokenResponse for this
   * user and scopes.
   *
   * @param userName The username of which to get the stored AccessTokenResponse
   * @return The AccessTokenResponse of the given username
   */
  public AccessTokenResponse getKeys(String userName);
}
File OAuthTokenDao.java
package com.google.oauthsample;

import com.google.api.client.auth.oauth2.draft10.AccessTokenResponse;
...

/**
 * Quick and Dirty memory implementation of {@link OAuthTokenDao} based on
 * HashMaps.
 */
public class OAuthTokenDaoMemoryImpl implements OAuthTokenDao {

  /** Object where all the Tokens will be stored */
  private static Map tokenPersistance = new HashMap();

  public void saveKeys(AccessTokenResponse tokens, String userName) {
    tokenPersistance.put(userName, tokens);
  }

  public AccessTokenResponse getKeys(String userName) {
    return tokenPersistance.get(userName);
  }
}
File OAuthTokenDaoMemoryImpl.java

Inoltre, le credenziali OAuth 2.0 per l'applicazione vengono archiviate in un file delle proprietà. In alternativa, puoi semplicemente averle come una costante da qualche parte in una delle tue classi Java, anche se qui ci sono la classe OAuthProperties e il file oauth.properties utilizzato nell'esempio:

package com.google.oauthsample;

import ...

/**
 * Object representation of an OAuth properties file.
 */
public class OAuthProperties {

  public static final String DEFAULT_OAUTH_PROPERTIES_FILE_NAME = "oauth.properties";

  /** The OAuth 2.0 Client ID */
  private String clientId;

  /** The OAuth 2.0 Client Secret */
  private String clientSecret;

  /** The Google APIs scopes to access */
  private String scopes;

  /**
   * Instantiates a new OauthProperties object reading its values from the
   * {@code OAUTH_PROPERTIES_FILE_NAME} properties file.
   *
   * @throws IOException IF there is an issue reading the {@code propertiesFile}
   * @throws OauthPropertiesFormatException If the given {@code propertiesFile}
   *           is not of the right format (does not contains the keys {@code
   *           clientId}, {@code clientSecret} and {@code scopes})
   */
  public OAuthProperties() throws IOException {
    this(OAuthProperties.class.getResourceAsStream(DEFAULT_OAUTH_PROPERTIES_FILE_NAME));
  }

  /**
   * Instantiates a new OauthProperties object reading its values from the given
   * properties file.
   *
   * @param propertiesFile the InputStream to read an OAuth Properties file. The
   *          file should contain the keys {@code clientId}, {@code
   *          clientSecret} and {@code scopes}
   * @throws IOException IF there is an issue reading the {@code propertiesFile}
   * @throws OAuthPropertiesFormatException If the given {@code propertiesFile}
   *           is not of the right format (does not contains the keys {@code
   *           clientId}, {@code clientSecret} and {@code scopes})
   */
  public OAuthProperties(InputStream propertiesFile) throws IOException {
    Properties oauthProperties = new Properties();
    oauthProperties.load(propertiesFile);
    clientId = oauthProperties.getProperty("clientId");
    clientSecret = oauthProperties.getProperty("clientSecret");
    scopes = oauthProperties.getProperty("scopes");
    if ((clientId == null) || (clientSecret == null) || (scopes == null)) {
      throw new OAuthPropertiesFormatException();
    }
  }

  /**
   * @return the clientId
   */
  public String getClientId() {
    return clientId;
  }

  /**
   * @return the clientSecret
   */
  public String getClientSecret() {
    return clientSecret;
  }

  /**
   * @return the scopes
   */
  public String getScopesAsString() {
    return scopes;
  }

  /**
   * Thrown when the OAuth properties file was not at the right format, i.e not
   * having the right properties names.
   */
  @SuppressWarnings("serial")
  public class OAuthPropertiesFormatException extends RuntimeException {
  }
}
File OAuthProprietà.java

Di seguito è riportato il file oauth.properties che contiene le credenziali OAuth 2.0 dell'applicazione. Devi modificare i valori di seguito manualmente.

# Client ID and secret. They can be found in the APIs console.
clientId=1234567890.apps.googleusercontent.com
clientSecret=aBcDeFgHiJkLmNoPqRsTuVwXyZ
# API scopes. Space separated.
scopes=https://www.googleapis.com/auth/tasks
File oauth.properties

L'ID client e il client secret OAuth 2.0 identificano l'applicazione e consentono all'API Tasks di applicare filtri e regole per le quote definiti per l'applicazione. L'ID client e il secret sono disponibili nella Console API di Google. Una volta aperta la console, dovrai:

  • Crea o seleziona un progetto.
  • Abilita l'API Tasks attivando lo stato dell'API Tasks nell'elenco dei servizi.
  • In Accesso API, crea un ID client OAuth 2.0, se non ne è ancora stato creato uno.
  • Assicurati che l'URL del gestore di callback del codice OAuth 2.0 del progetto sia registrato/autorizzato in URI di reindirizzamento. Ad esempio, in questo progetto di esempio dovrai registrare https://www.example.com/oauth2callback se la tua applicazione web viene gestita dal dominio https://www.example.com.

URI di reindirizzamento nella console API
URI di reindirizzamento nella console API

Ascolta il codice di autorizzazione dall'endpoint di autorizzazione di Google

Nel caso in cui l'utente non abbia ancora autorizzato l'applicazione ad accedere alle sue attività e pertanto sia stato reindirizzato all'endpoint di autorizzazione OAuth 2.0 di Google, all'utente viene visualizzata una finestra di dialogo di autorizzazione di Google che chiede all'utente di concedere all'applicazione l'accesso alle sue attività:

Finestra di dialogo dell&#39;autorizzazione di Google
Finestra di dialogo dell'autorizzazione di Google

Dopo aver concesso o negato l'accesso, l'utente verrà reindirizzato al gestore del callback del codice OAuth 2.0 che è stato specificato come reindirizzamento/callback durante la creazione dell'URL di autorizzazione di Google:

new GoogleAuthorizationRequestUrl(oauthProperties.getClientId(),
      OAuthCodeCallbackHandlerServlet.getOAuthCodeCallbackHandlerUrl(req), oauthProperties
          .getScopesAsString()).build()

Il gestore del callback del codice OAuth 2.0, OAuthCodeCallbackHandlerServlet, gestisce il reindirizzamento dall'endpoint OAuth 2.0 di Google. Esistono due casi da gestire:

  • L'utente ha concesso l'accesso: analizza la richiesta per ottenere il codice OAuth 2.0 dai parametri URL
  • L'utente ha negato l'accesso: mostra un messaggio all'utente

package com.google.oauthsample;

import ...

/**
 * Servlet handling the OAuth callback from the authentication service. We are
 * retrieving the OAuth code, then exchanging it for a refresh and an access
 * token and saving it.
 */
@SuppressWarnings("serial")
public class OAuthCodeCallbackHandlerServlet extends HttpServlet {

  /** The name of the Oauth code URL parameter */
  public static final String CODE_URL_PARAM_NAME = "code";

  /** The name of the OAuth error URL parameter */
  public static final String ERROR_URL_PARAM_NAME = "error";

  /** The URL suffix of the servlet */
  public static final String URL_MAPPING = "/oauth2callback";

  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // Getting the "error" URL parameter
    String[] error = req.getParameterValues(ERROR_URL_PARAM_NAME);

    // Checking if there was an error such as the user denied access
    if (error != null && error.length > 0) {
      resp.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, "There was an error: \""+error[0]+"\".");
      return;
    }
    // Getting the "code" URL parameter
    String[] code = req.getParameterValues(CODE_URL_PARAM_NAME);

    // Checking conditions on the "code" URL parameter
    if (code == null || code.length == 0) {
      resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "The \"code\" URL parameter is missing");
      return;
    }
  }

  /**
   * Construct the OAuth code callback handler URL.
   *
   * @param req the HttpRequest object
   * @return The constructed request's URL
   */
  public static String getOAuthCodeCallbackHandlerUrl(HttpServletRequest req) {
    String scheme = req.getScheme() + "://";
    String serverName = req.getServerName();
    String serverPort = (req.getServerPort() == 80) ? "" : ":" + req.getServerPort();
    String contextPath = req.getContextPath();
    String servletPath = URL_MAPPING;
    String pathInfo = (req.getPathInfo() == null) ? "" : req.getPathInfo();
    return scheme + serverName + serverPort + contextPath + servletPath + pathInfo;
  }
}
File OAuthCodeCallbackHandlerServlet.java

Scambia il codice di autorizzazione con un token di aggiornamento e accesso

Quindi, OAuthCodeCallbackHandlerServlet scambia il codice di autenticazione 2.0 con un token di aggiornamento e di accesso, lo rende persistenti nel datastore e reindirizza l'utente all'URL PrintTaskListsTitlesServlet:

Il codice aggiunto al file seguente è evidenziato dalla sintassi; il codice già esistente è visualizzato in grigio.

package com.google.oauthsample;

import ...

/**
 * Servlet handling the OAuth callback from the authentication service. We are
 * retrieving the OAuth code, then exchanging it for a refresh and an access
 * token and saving it.
 */
@SuppressWarnings("serial")
public class OAuthCodeCallbackHandlerServlet extends HttpServlet {

  /** The name of the Oauth code URL parameter */
  public static final String CODE_URL_PARAM_NAME = "code";

  /** The name of the OAuth error URL parameter */
  public static final String ERROR_URL_PARAM_NAME = "error";

  /** The URL suffix of the servlet */
  public static final String URL_MAPPING = "/oauth2callback";
/** L'URL a cui reindirizzare l'utente dopo la gestione del callback. Valuta la possibilità di * di salvarlo in un cookie prima di reindirizzare gli utenti all'URL di autorizzazione di Google * se hai più URL a cui reindirizzare gli utenti. */ Public static final String REDIRECT_URL = "/"; /** L'implementazione DAO del token OAuth. Valuta la possibilità di inserirlo anziché utilizzare * un'inizializzazione statica. Inoltre, utilizziamo una semplice implementazione della memoria * come esempio. Modifica l'implementazione in modo che utilizzi il tuo sistema di database. */ Public static OAuthTokenDao oauthTokenDao = new OAuthTokenDaoMemoryImpl}
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // Getting the "error" URL parameter
    String[] error = req.getParameterValues(ERROR_URL_PARAM_NAME);

    // Checking if there was an error such as the user denied access
    if (error != null && error.length > 0) {
      resp.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, "There was an error: \""+error[0]+"\".");
      return;
    }

    // Getting the "code" URL parameter
    String[] code = req.getParameterValues(CODE_URL_PARAM_NAME);

    // Checking conditions on the "code" URL parameter
    if (code == null || code.length == 0) {
      resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "The \"code\" URL parameter is missing");
      return;
    }
  /**
   * Construct the OAuth code callback handler URL.
   *
   * @param req the HttpRequest object
   * @return The constructed request's URL
   */
  public static String getOAuthCodeCallbackHandlerUrl(HttpServletRequest req) {
    String scheme = req.getScheme() + "://";
    String serverName = req.getServerName();
    String serverPort = (req.getServerPort() == 80) ? "" : ":" + req.getServerPort();
    String contextPath = req.getContextPath();
    String servletPath = URL_MAPPING;
    String pathInfo = (req.getPathInfo() == null) ? "" : req.getPathInfo();
    return scheme + serverName + serverPort + contextPath + servletPath + pathInfo;
  }
* * @param code Il codice è stato restituito dal servizio di autorizzazione * @param currentUrl l'URL del callback * @param oauthProperty L'oggetto contenente la configurazione OAuth * @return L'oggetto contenente sia un token di accesso che di aggiornamento * @throws IOauthException */ pubblici AccessTokenResponse ExchangeCodeForAccessAndTokenResponse ExchangeCodeForAccessAndTokenResponse ExchangeCodeForAccessAndTokenResponse ExchangeCode PropertyTransportdalegrgr.J.
File OAuthCodeCallbackHandlerServlet.java

Nota: l'implementazione descritta sopra utilizza alcune librerie di App Engine, che vengono utilizzate per semplificare l'implementazione. Se stai sviluppando per un'altra piattaforma, puoi implementare nuovamente l'interfaccia UserService che gestisce l'autenticazione utente.

Leggere le attività dell'utente e visualizzarle

L'utente ha concesso all'applicazione l'accesso alle sue attività. L'applicazione dispone di un token di aggiornamento che viene salvato nel datastore accessibile tramite OAuthTokenDao. Il servlet PrintTaskListsTitlesServlet ora può utilizzare questi token per accedere alle attività dell'utente e visualizzarle:

Il codice aggiunto al file seguente è evidenziato dalla sintassi; il codice già esistente è visualizzato in grigio.

package com.google.oauthsample;

import ...

/**
 * Simple sample Servlet which will display the tasks in the default task list of the user.
 */
@SuppressWarnings("serial")
public class PrintTasksTitlesServlet extends HttpServlet {

  /**
   * The OAuth Token DAO implementation, used to persist the OAuth refresh token.
   * Consider injecting it instead of using a static initialization. Also we are
   * using a simple memory implementation as a mock. Change the implementation to
   * using your database system.
   */
  public static OAuthTokenDao oauthTokenDao = new OAuthTokenDaoMemoryImpl();

  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // Getting the current user
    // This is using App Engine's User Service but you should replace this to
    // your own user/login implementation
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();

    // If the user is not logged-in it is redirected to the login service, then back to this page
    if (user == null) {
      resp.sendRedirect(userService.createLoginURL(getFullRequestUrl(req)));
      return;
    }

    // Checking if we already have tokens for this user in store
    AccessTokenResponse accessTokenResponse = oauthTokenDao.getKeys(user.getEmail());

    // If we don't have tokens for this user
    if (accessTokenResponse == null) {
      OAuthProperties oauthProperties = new OAuthProperties();
      // Redirect to the Google OAuth 2.0 authorization endpoint
      resp.sendRedirect(new GoogleAuthorizationRequestUrl(oauthProperties.getClientId(),
          OAuthCodeCallbackHandlerServlet.getOAuthCodeCallbackHandlerUrl(req), oauthProperties
              .getScopesAsString()).build());
      return;
    }
// La stampa dell'elenco delle attività dell'utente nella risposta resp.setContentType("text/plain"); resp.getWriter().append("Elenchi di attività per l'utente " + user.getEmail() + ":\n\n"); printTasksTitles(accessTokenResponse, resp.getWriter( apporta)
  }

  /**
   * Construct the request's URL without the parameter part.
   *
   * @param req the HttpRequest object
   * @return The constructed request's URL
   */
  public static String getFullRequestUrl(HttpServletRequest req) {
    String scheme = req.getScheme() + "://";
    String serverName = req.getServerName();
    String serverPort = (req.getServerPort() == 80) ? "" : ":" + req.getServerPort();
    String contextPath = req.getContextPath();
    String servletPath = req.getServletPath();
    String pathInfo = (req.getPathInfo() == null) ? "" : req.getPathInfo();
    String queryString = (req.getQueryString() == null) ? "" : "?" + req.getQueryString();
    return scheme + serverName + serverPort + contextPath + servletPath + pathInfo + queryString;
  }
/** * Utilizza l'API di Google Tasks nell'elenco delle attività predefinite per recuperare un elenco * * @param accessTokenResponse L'oggetto AccessTokenResponse OAuth 2.0 * contenente il token di accesso e un token di aggiornamento. * @param genera l'autore del flusso di output dove ritoccare le attività elenca i titoli * @return Un elenco dei titoli delle attività degli utenti nell'elenco delle attività predefinito. * @throws IOException */ public void printTasksTitles(AccessTokenResponse accessTokenResponse, Writer output) restituisce IOException { // Initializing the Tasks service HttpTransport trasporta = new NetHttpTransport() JsonFactory jsonFactory = new JacksonTaskFactory AccessProtected
File PrintTasksTitlesServlet.java

L'utente verrà visualizzato con le sue attività:

Le attività dell&#39;utente
Attività dell'utente

Applicazione di esempio

Il codice per questa applicazione di esempio può essere scaricato qui. Non esitare a dare un'occhiata.