Uso de OAuth 2.0 con la biblioteca cliente de la API de Google para Java

Descripción general

Propósito: En este documento, se explica cómo usar la clase de utilidad GoogleCredential para realizar la autorización de OAuth 2.0 con los servicios de Google. Para obtener información sobre las funciones genéricas de OAuth 2.0 que proporcionamos, consulta OAuth 2.0 y la biblioteca cliente de Google OAuth para Java.

Resumen: Para acceder a los datos protegidos almacenados en los servicios de Google, usa OAuth 2.0 para la autorización. Las API de Google admiten flujos de OAuth 2.0 para diferentes tipos de aplicaciones cliente. En todos estos flujos, la aplicación cliente solicita un token de acceso que se asocia solo con tu aplicación cliente y con el propietario de los datos protegidos a los que se accede. El token de acceso también está asociado con un alcance limitado que define el tipo de datos a los que tiene acceso tu aplicación cliente (por ejemplo, “Administra tus tareas”). Un objetivo importante para OAuth 2.0 es proporcionar un acceso seguro y práctico a los datos protegidos, a la vez que se minimiza el posible impacto si se roba un token de acceso.

Los paquetes de OAuth 2.0 en la biblioteca cliente de la API de Google para Java se basan en la biblioteca cliente de Google OAuth 2.0 para Java de uso general.

Para obtener más información, consulta la documentación de Javadoc para los siguientes paquetes:

Consola de API de Google

Antes de acceder a las API de Google, debes configurar un proyecto en la Consola de API de Google para autenticación y facturación, ya sea que tu cliente sea una aplicación instalada, una aplicación para dispositivos móviles, un servidor web o un cliente que se ejecute en el navegador.

Para obtener instrucciones sobre cómo configurar tus credenciales de forma correcta, consulta la Ayuda de la Consola de API.

Credencial

GoogleCredential

GoogleCredential es una clase auxiliar segura para subprocesos de OAuth 2.0 a fin de acceder a recursos protegidos mediante un token de acceso. Por ejemplo, si ya tienes un token de acceso, puedes realizar una solicitud de la siguiente manera:

GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Plus plus = new Plus.builder(new NetHttpTransport(),
                             GsonFactory.getDefaultInstance(),
                             credential)
    .setApplicationName("Google-PlusSample/1.0")
    .build();

Identidad de Google App Engine

Esta credencial alternativa se basa en la API de Java de App Identity para Google App Engine. A diferencia de la credencial en la que una aplicación cliente solicita acceso a los datos de un usuario final, la API de App Identity proporciona acceso a los datos propios de la aplicación cliente.

Usa AppIdentityCredential (de google-api-client-appengine). Esta credencial es mucho más simple porque Google App Engine se encarga de todos los detalles. Solo debes especificar el alcance de OAuth 2.0 que necesitas.

Ejemplo de código tomado de urlshortener-robots-appengine-sample:

static Urlshortener newUrlshortener() {
  AppIdentityCredential credential =
      new AppIdentityCredential(
          Collections.singletonList(UrlshortenerScopes.URLSHORTENER));
  return new Urlshortener.Builder(new UrlFetchTransport(),
                                  GsonFactory.getDefaultInstance(),
                                  credential)
      .build();
}

Almacén de datos

Por lo general, un token de acceso tiene una fecha de vencimiento de 1 hora, después de la cual recibirás un error si intentas usarlo. GoogleCredential se encarga de actualizar automáticamente el token, lo que significa obtener un nuevo token de acceso. Esto se hace mediante un token de actualización de larga duración, que se suele recibir junto con el token de acceso si usas el parámetro access_type=offline durante el flujo de código de autorización (consulta GoogleAuthorizationCodeFlow.Builder.setAccessType(String)).

La mayoría de las aplicaciones necesitarán conservar el token de acceso o el token de actualización. Para conservar los tokens de acceso o actualización de las credenciales, puedes proporcionar tu propia implementación de DataStoreFactory con StoredCredential o puedes usar una de las siguientes implementaciones que proporciona la biblioteca:

Usuarios de App Engine: AppEngineCredentialStore dejó de estar disponible y se quitará pronto. Te recomendamos que uses AppEngineDataStoreFactory con StoredCredential. Si tienes credenciales almacenadas de la manera anterior, puedes usar los métodos auxiliares agregados migrateTo(AppEngineDataStoreFactory) o migrateTo(DataStore) para realizar la migración.

Puedes usar DataStoreCredentialRefreshListener y configurarlo para la credencial mediante GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).

Flujo de código de autorización

Usa el flujo de código de autorización para permitir que el usuario final otorgue a tu aplicación acceso a sus datos protegidos en las API de Google. El protocolo para este flujo se especifica en Otorgamiento de código de autorización.

Este flujo se implementa mediante GoogleAuthorizationCodeFlow. Estos son los pasos:

De manera alternativa, si no usas GoogleAuthorizationCodeFlow, puedes usar las clases de nivel inferior:

Cuando configuras tu proyecto en la Consola de API de Google, seleccionas entre diferentes credenciales, según el flujo que uses. Para obtener más detalles, consulta Configura situaciones de OAuth 2.0 y OAuth 2.0. Los fragmentos de código para cada flujo se encuentran a continuación.

Aplicaciones de servidor web

El protocolo para este flujo se explica en Usa OAuth 2.0 para aplicaciones de servidor web.

Esta biblioteca proporciona clases auxiliares de servlet a fin de simplificar de manera significativa el flujo de código de autorización para casos de uso básicos. Solo debes proporcionar subclases concretas de AbstractAuthorizationCodeServlet y AbstractAuthorizationCodeCallbackServlet (de google-oauth-client-servlet) y agregarlas a tu archivo web.xml. Ten en cuenta que aún debes encargarte del acceso del usuario para tu aplicación web y extraer un ID del usuario.

public class CalendarServletSample 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 GoogleAuthorizationCodeFlow.Builder(
        new NetHttpTransport(), GsonFactory.getDefaultInstance(),
        "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
        Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

public class CalendarServletCallbackSample 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 GoogleAuthorizationCodeFlow.Builder(
        new NetHttpTransport(), GsonFactory.getDefaultInstance()
        "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
        Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

Aplicaciones de Google App Engine

El flujo de código de autorización en App Engine es casi idéntico al flujo de código de autorización del servlet, excepto que podemos aprovechar la API de Java de usuarios de Google App Engine. El usuario debe acceder para que se habilite la API de usuarios de Java. Si deseas obtener más información sobre cómo redireccionar a los usuarios a una página de acceso si aún no lo hicieron, consulta Seguridad y autenticación (en web.xml).

La diferencia principal con el caso de servlet es que proporcionas subclases concretas de AbstractAppEngineAuthorizationCodeServlet y AbstractAppEngineAuthorizationCodeCallbackServlet (de google-oauth-client-appengine). Extienden las clases de servlet abstractas y, luego, implementan el método getUserId por ti con la API de Java de usuarios. AppEngineDataStoreFactory (de google-http-client-appengine) es una buena opción para conservar la credencial mediante la API de almacenamiento de datos de Google App Engine.

Ejemplo tomado (ligeramente modificado) de calendar-appengine-sample:

public class CalendarAppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    return Utils.getRedirectUri(req);
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return Utils.newFlow();
  }
}

class Utils {
  static String getRedirectUri(HttpServletRequest req) {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  static GoogleAuthorizationCodeFlow newFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
        getClientCredential(), Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }
}

public class OAuth2Callback extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  private static final long serialVersionUID = 1L;

  @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 {
    String nickname = UserServiceFactory.getUserService().getCurrentUser().getNickname();
    resp.getWriter().print("<h3>" + nickname + ", why don't you want to play with me?</h1>");
    resp.setStatus(200);
    resp.addHeader("Content-Type", "text/html");
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    return Utils.getRedirectUri(req);
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return Utils.newFlow();
  }
}

Para obtener una muestra adicional, consulta storage-serviceaccount-appengine-sample.

Cuentas de servicio

GoogleCredential también admite cuentas de servicio. A diferencia de la credencial en la que una aplicación cliente solicita acceso a los datos de un usuario final, las cuentas de servicio proporcionan acceso a los datos propios de la aplicación cliente. Tu aplicación cliente firma la solicitud de un token de acceso con una clave privada descargada desde la Consola de API de Google.

Ejemplo de código tomado de plus-serviceaccount-cmdline-sample:

HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
...
// Build service account credential.

GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json"))
    .createScoped(Collections.singleton(PlusScopes.PLUS_ME));
// Set up global Plus instance.
plus = new Plus.Builder(httpTransport, jsonFactory, credential)
    .setApplicationName(APPLICATION_NAME).build();
...

Para obtener una muestra adicional, consulta storage-serviceaccount-cmdline-sample.

Robo de identidad

También puedes usar el flujo de la cuenta de servicio para robar la identidad de un usuario en un dominio que te pertenece. Esto es muy similar al flujo de la cuenta de servicio anterior, pero también debes llamar a GoogleCredential.Builder.setServiceAccountUser(String).

Aplicaciones instaladas

Este es el flujo de código de autorización de la línea de comandos que se describe en Usar OAuth 2.0 para aplicaciones instaladas.

Ejemplo de fragmento de plus-cmdline-sample:

public static void main(String[] args) {
  try {
    httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR);
    // authorization
    Credential credential = authorize();
    // set up global Plus instance
    plus = new Plus.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName(
        APPLICATION_NAME).build();
   // ...
}

private static Credential authorize() throws Exception {
  // load client secrets
  GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
      new InputStreamReader(PlusSample.class.getResourceAsStream("/client_secrets.json")));
  // set up authorization code flow
  GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
      httpTransport, JSON_FACTORY, clientSecrets,
      Collections.singleton(PlusScopes.PLUS_ME)).setDataStoreFactory(
      dataStoreFactory).build();
  // authorize
  return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
}

Aplicaciones del cliente

Si deseas usar el flujo de cliente basado en el navegador descrito en Usa OAuth 2.0 para aplicaciones del cliente, por lo general, debes seguir estos pasos:

  1. Redirecciona al usuario final en el navegador a la página de autorización con GoogleBrowserClientRequestUrl para otorgar a la aplicación del navegador acceso a los datos protegidos del usuario final.
  2. Usa la biblioteca cliente de las API de Google para JavaScript a fin de procesar el token de acceso que se encuentra en el fragmento de URL en el URI de redireccionamiento registrado en la Consola de API de Google.

Ejemplo de uso para una aplicación web:

public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException {
  String url = new GoogleBrowserClientRequestUrl("812741506391.apps.googleusercontent.com",
      "https://oauth2.example.com/oauthcallback", Arrays.asList(
          "https://www.googleapis.com/auth/userinfo.email",
          "https://www.googleapis.com/auth/userinfo.profile")).setState("/profile").build();
  response.sendRedirect(url);
}

Android

@Beta

Qué biblioteca usar con Android:

Si desarrollas apps para Android y la API de Google que quieres usar se incluye en la biblioteca de Servicios de Google Play, usa esa biblioteca para obtener el mejor rendimiento y la mejor experiencia. Si la API de Google que quieres usar con Android no forma parte de la biblioteca de Servicios de Google Play, puedes usar la biblioteca cliente de la API de Google para Java, que es compatible con Android 4.0 (Ice Cream Sandwich) (o versiones posteriores) y que se describe aquí. La compatibilidad de Android en la biblioteca cliente de la API de Google para Java es @Beta.

Información general:

A partir de Eclair (SDK 2.1), las cuentas de usuario se administran en un dispositivo Android con el administrador de cuentas. El SDK administra de forma central toda la autorización de las aplicaciones para Android mediante AccountManager. Especifica el alcance de OAuth 2.0 que necesita tu aplicación y muestra un token de acceso para usar.

El alcance de OAuth 2.0 se especifica mediante el parámetro authTokenType como oauth2: más el alcance. Por ejemplo:

oauth2:https://www.googleapis.com/auth/tasks

Especifica el acceso de lectura y escritura a la API de Google Tasks. Si necesitas varios alcances de OAuth 2.0, usa una lista separada por espacios.

Algunas API tienen parámetros authTokenType especiales que también funcionan. Por ejemplo, “Administrar tus tareas” es un alias para el ejemplo de authtokenType que se muestra arriba.

También debes especificar la clave de API desde la Consola de API de Google. De lo contrario, el token que te proporciona el Administrador de cuentas solo te proporciona una cuota anónima, que suele ser muy baja. Por el contrario, si especificas una clave de API, recibirás una cuota gratuita más alta y, de manera opcional, puedes configurar la facturación para su uso superior.

Ejemplo de fragmento de código tomado de tasks-android-sample:

com.google.api.services.tasks.Tasks service;

@Override
public void onCreate(Bundle savedInstanceState) {
  credential =
      GoogleAccountCredential.usingOAuth2(this, Collections.singleton(TasksScopes.TASKS));
  SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
  credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
  service =
      new com.google.api.services.tasks.Tasks.Builder(httpTransport, jsonFactory, credential)
          .setApplicationName("Google-TasksAndroidSample/1.0").build();
}

private void chooseAccount() {
  startActivityForResult(credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  switch (requestCode) {
    case REQUEST_GOOGLE_PLAY_SERVICES:
      if (resultCode == Activity.RESULT_OK) {
        haveGooglePlayServices();
      } else {
        checkGooglePlayServicesAvailable();
      }
      break;
    case REQUEST_AUTHORIZATION:
      if (resultCode == Activity.RESULT_OK) {
        AsyncLoadTasks.run(this);
      } else {
        chooseAccount();
      }
      break;
    case REQUEST_ACCOUNT_PICKER:
      if (resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) {
        String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME);
        if (accountName != null) {
          credential.setSelectedAccountName(accountName);
          SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
          SharedPreferences.Editor editor = settings.edit();
          editor.putString(PREF_ACCOUNT_NAME, accountName);
          editor.commit();
          AsyncLoadTasks.run(this);
        }
      }
      break;
  }
}