Build handler של קריאה חוזרת להרשאות

במאמר הזה מוסבר איך להטמיע handler של קריאה חוזרת לאימות OAuth 2.0 באמצעות Java servlets דרך אפליקציית אינטרנט לדוגמה שמציגה את המשימות של המשתמש באמצעות Google Tasks API. אפליקציית הדוגמה מבקשת קודם הרשאה לגשת אל Google Tasks של המשתמש, ואז מציגה את המשימות של המשתמש ברשימת המשימות שמוגדרת כברירת מחדל.

קהל

המסמך הזה מיועד לאנשים שמכירים את ארכיטקטורת אפליקציות האינטרנט של Java ו-J2EE. מומלץ להכיר את תהליך ההרשאה של OAuth 2.0.

תוכן עניינים

כדי לקבל דוגמה כזו שפועלת באופן מלא, צריך לבצע כמה שלבים:

הצהרה על מיפויים של סרוולטים בקובץ web.xml

האפליקציה הזו משתמשת בשני סרוולטים:

  • PrintTasksTitlesServlet (ממופה ל-/): נקודת הכניסה של האפליקציה שתטפל באימות המשתמש ותציג את המשימות של המשתמש
  • OAuthCodeCallbackHandlerServlet (ממופה ל-/oauth2callback): קריאה חוזרת (callback) של OAuth 2.0 שמטפלת בתגובה מנקודת הקצה של הרשאת OAuth

קובץ web.xml הבא שממפה את 2 הסרוולטים האלה לכתובות URL באפליקציה שלנו:

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

אימות משתמשים במערכת שלהם ובקשת הרשאה לגישה למשימות שלהם

המשתמש נכנס לאפליקציה דרך כתובת ה-URL של הבסיס '/' שממופה ל-servlet‏ PrintTaskListsTitlesServlet. ב-servlet הזה, מבוצעות המשימות הבאות:

  • בודקת אם המשתמש מאומת במערכת.
  • אם המשתמש לא מאומת, הוא מופנה לדף האימות.
  • אם המשתמש מאומת, מתבצעת בדיקה של אסימון רענון שכבר נמצא באחסון הנתונים, והטיפול בבדיקה הזו מתבצע על ידי OAuthTokenDao שמופיע בהמשך. אם האסימונים לא זמינים באחסון של המשתמש, זה אומר שהמשתמש עדיין לא העניק לאפליקציה הרשאה לגשת למשימות שלו. לאחר מכן המשתמש מועבר לנקודת הקצה של Google למתן הרשאות OAuth 2.0.

כך אפשר להטמיע את זה:

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. Additionally, a
   * simple memory implementation is used as a mock. Change the implementation to
   * using the user's own user/login implementation.
   */
  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 tokens are not available for this user
    if (accessTokenResponse == null) {
      OAuthProperties oauthProperties = new OAuthProperties();
      // Redirects 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;
  }
}

הערה: בהטמעה הקודמת נעשה שימוש בספריות מסוימות של App Engine. הן משמשות לצורך פשטות. אם אתם מפתחים עבור פלטפורמה אחרת, צריך להטמיע מחדש את הממשק UserService שמטפל באימות משתמשים.

האפליקציה משתמשת ב-DAO כדי לשמור את אסימוני ההרשאה של המשתמש ולגשת אליהם. הממשק OAuthTokenDao והטמעה מדומה (בזיכרון) – OAuthTokenDaoMemoryImpl – שמשמשים בדוגמה הזו, מוצגים בדוגמאות הבאות:

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);
}
קובץ 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<String, AccessTokenResponse> tokenPersistance = new HashMap<String, AccessTokenResponse>();

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

  public AccessTokenResponse getKeys(String userName) {
    return tokenPersistance.get(userName);
  }
}
קובץ OAuthTokenDaoMemoryImpl.java

פרטי הכניסה של OAuth 2.0 לאפליקציה מאוחסנים בקובץ מאפיינים. אפשרות אחרת היא לאחסן אותם כקבוע במקום כלשהו באחד משיעורי ה-Java. הנה המחלקה OAuthProperties וקובץ oauth.properties שמשמשים בדוגמה:

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 in the correct format (does not contain 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 {
  }
}
קובץ OAuthProperties.java

בדוגמה הבאה מוצג הקובץ oauth.properties, שמכיל את פרטי הכניסה של OAuth 2.0 לאפליקציה. צריך לשנות את הערכים בקובץ הזה.

# 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
קובץ oauth.properties

מזהה הלקוח וסוד הלקוח ב-OAuth 2.0 מזהים את האפליקציה ומאפשרים ל-Tasks API להחיל מסננים וכללי מכסה שמוגדרים לאפליקציה. אפשר למצוא את מזהה הלקוח ואת הסוד שלו ב-Google APIs Console. אחרי הכניסה למסוף, המשתמש צריך:

  • יוצרים או בוחרים פרויקט.
  • כדי להפעיל את Tasks API, מגדירים את הסטטוס של Tasks API לON ברשימת השירותים.
  • בקטע API Access (גישה ל-API), יוצרים מזהה לקוח OAuth 2.0 אם עדיין לא נוצר כזה.
  • מוודאים שכתובת ה-URL של ה-handler של קוד ה-callback של OAuth 2.0 בפרויקט רשומה בכתובות ה-URI להפניה אוטומטית או שנוספה לרשימת ההיתרים. לדוגמה, בפרויקט לדוגמה הזה, המשתמש יצטרך להירשם https://www.example.com/oauth2callback אם אפליקציית האינטרנט מוגשת מהדומיין https://www.example.com.

‫URI של הפניה אוטומטית ב-APIs Console
‫URI של הפניה אוטומטית ב-APIs Console

טיפול בקוד ההרשאה מנקודת הקצה להרשאה של Google

במקרה שבו המשתמש עדיין לא אישר לאפליקציה לגשת למשימות שלו, ולכן הוא מועבר לנקודת הקצה של Google לאישור OAuth 2.0, מוצג למשתמש תיבת דו-שיח לאישור מ-Google שבה הוא מתבקש לאשר לאפליקציה לגשת למשימות שלו:

תיבת הדו-שיח של Google למתן הרשאה
תיבת הדו-שיח של Google למתן הרשאה

אחרי שהמשתמש מאשר או דוחה את הגישה, הוא מופנה חזרה ל-handler של קוד ה-callback של OAuth 2.0 שצוין כהפניה או כ-callback כשנוצרה כתובת ה-URL של ההרשאה של Google:

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

ה-OAuth 2.0 code callback handler – OAuthCodeCallbackHandlerServlet – מטפל בהפניה מנקודת הקצה של Google OAuth 2.0. יש 2 מקרים שצריך לטפל בהם:

  • המשתמש העניק גישה: הבקשה מנותחת כדי לקבל את קוד OAuth 2.0 מפרמטרים של כתובת ה-URL.
  • המשתמש דחה את הגישה: מוצגת למשתמש הודעה.
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;
  }
}
קובץ OAuthCodeCallbackHandlerServlet.java

החלפת קוד ההרשאה באסימון רענון ובאסימון גישה

לאחר מכן, OAuthCodeCallbackHandlerServlet מחליף את קוד Auth 2.0 באסימוני רענון וגישה, שומר אותו במאגר הנתונים ומפנה את המשתמש בחזרה לכתובת ה-URL של OAuthCodeCallbackHandlerServlet:PrintTaskListsTitlesServlet

הקוד שנוסף לקובץ מודגש.

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";
  /** The URL to redirect the user to after handling the callback. Consider
   * saving this in a cookie before redirecting users to the Google
   * authorization URL if you have multiple possible URL to redirect people to. */
  public static final String REDIRECT_URL = "/";

  /** The OAuth Token DAO implementation. 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 "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 incoming request URL
    String requestUrl = getOAuthCodeCallbackHandlerUrl(req);

    // Exchange the code for OAuth tokens
    AccessTokenResponse accessTokenResponse = exchangeCodeForAccessAndRefreshTokens(code[0],
        requestUrl);

    // Getting the current user
    // This is using App Engine's User Service, but the user should replace this
    // with their own user/login implementation
    UserService userService = UserServiceFactory.getUserService();
    String email = userService.getCurrentUser().getEmail();

    // Save the tokens
    oauthTokenDao.saveKeys(accessTokenResponse, email);

    resp.sendRedirect(REDIRECT_URL);
  }

  /**
   * 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;
  }
  /**
   * Exchanges the given code for an exchange and a refresh token.
   *
   * @param code The code gotten back from the authorization service
   * @param currentUrl The URL of the callback
   * @param oauthProperties The object containing the OAuth configuration
   * @return The object containing both an access and refresh token
   * @throws IOException
   */
  public AccessTokenResponse exchangeCodeForAccessAndRefreshTokens(String code, String currentUrl)
      throws IOException {

    HttpTransport httpTransport = new NetHttpTransport();
    JacksonFactory jsonFactory = new JacksonFactory();

    // Loading the oauth config file
    OAuthProperties oauthProperties = new OAuthProperties();

    return new GoogleAuthorizationCodeGrant(httpTransport, jsonFactory, oauthProperties
        .getClientId(), oauthProperties.getClientSecret(), code, currentUrl).execute();
  }
}
קובץ OAuthCodeCallbackHandlerServlet.java

הערה: בהטמעה הקודמת נעשה שימוש בספריות מסוימות של App Engine, לצורך פשטות. אם אתם מפתחים עבור פלטפורמה אחרת, צריך להטמיע מחדש את הממשק UserService שמטפל באימות משתמשים.

קריאת המשימות של המשתמש והצגתן

המשתמש העניק לאפליקציה גישה למשימות שלו. לאפליקציה יש טוקן רענון שנשמר במאגר הנתונים שאפשר לגשת אליו דרך OAuthTokenDao. ה-servlet‏ PrintTaskListsTitlesServlet יכול עכשיו להשתמש בטוקנים האלה כדי לגשת למשימות של המשתמש ולהציג אותן:

הקוד שנוסף לקובץ מודגש.

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. Additionally, a
   * simple memory implementation is used as a mock. Change the implementation to
   * use your own 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;
    }
// Prints the user's task list titles in the response
    resp.setContentType("text/plain");
    resp.getWriter().append("Task Lists titles for user " + user.getEmail() + ":\n\n");
    printTasksTitles(accessTokenResponse, resp.getWriter());
  }

  /**
   * 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;
  }
  /**
   * Uses the Google Tasks API to retrieve a list of the user's tasks in the default
   * tasks list.
   *
   * @param accessTokenResponse The OAuth 2.0 AccessTokenResponse object
   *          containing the access token and a refresh token.
   * @param output The output stream writer to write the task list titles to.
   * @return A list of the user's task titles in the default task list.
   * @throws IOException
   */
  public void printTasksTitles(AccessTokenResponse accessTokenResponse, Writer output) throws IOException {

    // Initializing the Tasks service
    HttpTransport transport = new NetHttpTransport();
    JsonFactory jsonFactory = new JacksonFactory();
    OAuthProperties oauthProperties = new OAuthProperties();

    GoogleAccessProtectedResource accessProtectedResource = new GoogleAccessProtectedResource(
        accessTokenResponse.accessToken, transport, jsonFactory, oauthProperties.getClientId(),
        oauthProperties.getClientSecret(), accessTokenResponse.refreshToken);

    Tasks service = new Tasks(transport, accessProtectedResource, jsonFactory);

    // Using the initialized Tasks API service to query the list of tasks lists
    com.google.api.services.tasks.model.Tasks tasks = service.tasks.list("@default").execute();

    for (Task task : tasks.items) {
      output.append(task.title + "\n");
    }
  }
}
קובץ PrintTasksTitlesServlet.java

המשימות של המשתמש מוצגות:

רשימה של המשימות של המשתמש
המשימות של המשתמש

אפליקציה לדוגמה

אפשר להוריד את הקוד של האפליקציה לדוגמה הזו.