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

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

קהל

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

תוכן עניינים

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

הצהרת מיפויים של servlet בקובץ web.xml

נשתמש בשני servlets באפליקציה:

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

בהמשך מופיע קובץ web.xml שממפה את 2 ה-servlets האלה לכתובות 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 ברמה הבסיסית '‎/', שממופת לסרלט PrintTaskListsTitlesServlet. ב-servlet הזה מתבצעות המשימות הבאות:

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

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;
  }
}
הקובץ PrintTasksTitlesServlet.java

הערה: בהטמעה שלמעלה נעשה שימוש בספריות מסוימות של 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 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 {
  }
}
הקובץ 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. במסוף, תצטרכו:

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

ה-URI של ההפניה האוטומטית במסוף APIs
ה-URI של ההפניה האוטומטית במסוף ממשקי ה-API

האזנה לקוד ההרשאה מנקודת הקצה להרשאה של Google

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

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

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

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

הטיפול בהפניה אוטומטית מנקודת הקצה של Google OAuth 2.0 מתבצע על ידי ה-handler של הקריאה החוזרת (callback) עם קוד של OAuth 2.0 – OAuthCodeCallbackHandlerServlet. יש 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

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

לאחר מכן, ה-servlet OAuthCodeCallbackHandlerServlet מחליף את קוד Auth 2.0 באסימוני רענון ואסימוני גישה, שומר אותו במאגר הנתונים ומפנה את המשתמש חזרה לכתובת ה-URL 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";
/** כתובת ה-URL שאליה מופנה המשתמש אחרי הטיפול בקריאה החוזרת. אם יש לכם כמה כתובות URL אפשריות להפניה מחדש של אנשים, כדאי לשמור את הפרטים האלה בקובץ cookie לפני שמפנים את המשתמשים לכתובת ה-URL של הרשאת Google. */ public static final String REDIRECT_URL = "/"; /** The OAuth Token DAO implementation. מומלץ להחדיר אותו במקום להשתמש * בהפעלה סטטית. בנוסף, אנחנו משתמשים בהטמעה פשוטה של זיכרון * בתור תרמית. משנים את ההטמעה כך שתשתמש במערכת מסד הנתונים שלכם. */ 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;
    }
// יצירת כתובת ה-URL של הבקשה הנכנסת String requestUrl = getOAuthCodeCallbackHandlerUrl(req); // המרת הקוד לאסימוני OAuth AccessTokenResponse accessTokenResponse = exchangeCodeForAccessAndRefreshTokens(code[0], requestUrl); // אחזור המשתמש הנוכחי // הקוד הזה משתמש בשירות המשתמשים של App Engine, אבל כדאי להחליף אותו בהטמעה משלכם של משתמשים/כניסה UserService userService = UserServiceFactory.getUserService(); String email = userService.getCurrentUser().getEmail(); // שמירת האסימונים 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;
  }
/** * הקוד הזה מחליף אסימון המרה ואסימון רענון. * * @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 tokens * @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. מעכשיו, הסרלט 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. 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;
    }
// הדפסת השמות של רשימות המשימות של המשתמש בתגובה 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;
  }
/** * משתמשים ב-Google Tasks API כדי לאחזר רשימה של המשימות של המשתמשים ברשימת המשימות שמוגדרת כברירת מחדל. * * @param accessTokenResponse אובייקט AccessTokenResponse של OAuth 2.0 * שמכיל את אסימון הגישה ואת אסימון הרענון. * @param output הסופר של פלט הזרם שבו רושמים את שמות רשימות המשימות * @return רשימה של שמות המשימות של המשתמשים ברשימת המשימות שמוגדרת כברירת מחדל. * @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

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

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

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

אפשר להוריד את הקוד של האפליקציה לדוגמה הזו כאן. אפשר להיכנס ולעיין בו.