Como usar o OAuth 2.0 com a biblioteca de cliente da API do Google para Java

Informações gerais

Objetivo:este documento explica como usar a classe de utilitário GoogleCredential para fazer a autorização do OAuth 2.0 com os serviços do Google. Para mais informações sobre as funções genéricas do OAuth 2.0 que fornecemos, consulte OAuth 2.0 e a biblioteca de cliente do Google OAuth para Java.

Resumo:para acessar dados protegidos armazenados nos serviços do Google, use o OAuth 2.0 para autorização. As APIs do Google são compatíveis com fluxos do OAuth 2.0 para diferentes tipos de aplicativos clientes. Em todos esses fluxos, o aplicativo cliente solicita um token de acesso que está associado apenas ao seu aplicativo cliente e ao proprietário dos dados protegidos que estão sendo acessados. O token de acesso também é associado a um escopo limitado que define o tipo de dados a que seu aplicativo cliente tem acesso (por exemplo, "Gerenciar suas tarefas"). Um objetivo importante do OAuth 2.0 é fornecer acesso seguro e conveniente aos dados protegidos, minimizando o possível impacto caso um token de acesso seja roubado.

Os pacotes do OAuth 2.0 na biblioteca de cliente da API do Google para Java são criados com base na biblioteca de cliente do Google OAuth 2.0 para Java de uso geral.

Para mais detalhes, consulte a documentação do Javadoc para os seguintes pacotes:

Console de APIs do Google

Antes de acessar as APIs do Google, você precisa configurar um projeto no Console de APIs do Google para fins de autenticação e faturamento, independentemente de o cliente ser um aplicativo instalado, um aplicativo para dispositivos móveis, um servidor da Web ou executado em um navegador.

Para instruções sobre como configurar suas credenciais corretamente, consulte a Ajuda do Console de APIs.

Credencial

GoogleCredential

GoogleCredential é uma classe auxiliar segura para linhas de execução do OAuth 2.0 para acessar recursos protegidos usando um token de acesso. Por exemplo, se você já tiver um token de acesso, poderá fazer uma solicitação desta maneira:

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

Identidade do Google App Engine

Essa credencial alternativa é baseada na API App Identity Java do Google App Engine. Ao contrário da credencial em que um aplicativo cliente solicita acesso aos dados de um usuário final, a API App Identity fornece acesso aos dados do próprio aplicativo cliente.

Use AppIdentityCredential (de google-api-client-appengine). Essa credencial é muito mais simples porque o Google App Engine cuida de todos os detalhes. Você especifica apenas o escopo do OAuth 2.0 necessário.

Exemplo de código retirado 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();
}

Armazenamento de dados

O token de acesso geralmente tem uma data de validade de uma hora. Após esse período, você receberá um erro se tentar usá-lo. GoogleCredential faz a "atualização" automática do token, o que significa simplesmente receber um novo token de acesso. Isso é feito usando um token de atualização de longa duração, que normalmente é recebido com o token de acesso se você usa o parâmetro access_type=offline durante o fluxo do código de autorização (consulte GoogleAuthorizationCodeFlow.Builder.setAccessType(String)).

A maioria dos aplicativos precisará manter o token de acesso e/ou o token de atualização da credencial. Para manter os tokens de acesso e/ou atualização da credencial, forneça sua própria implementação de DataStoreFactory com StoredCredential ou use uma das seguintes implementações fornecidas pela biblioteca:

Usuários do App Engine: o AppEngineCredentialStore foi descontinuado e será removido em breve. Recomendamos que você use o AppEngineDataStoreFactory com StoredCredential. Se você tiver credenciais armazenadas de maneira antiga, poderá usar os métodos auxiliares MigrateTo(AppEngineDataStoreFactory) ou MigrateTo(DataStore) adicionados para fazer a migração.

É possível usar o DataStoreCredentialRefreshListener e defini-lo para a credencial com GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).

Fluxo do código de autorização

Use o fluxo do código de autorização para permitir que o usuário final conceda ao seu aplicativo acesso aos dados protegidos nas APIs do Google. O protocolo para esse fluxo é especificado em Concessão de código de autorização.

Esse fluxo é implementado usando GoogleAuthorizationCodeFlow. Essas etapas são:

Como alternativa, se você não estiver usando o GoogleAuthorizationCodeFlow, poderá usar as classes de nível inferior:

Ao configurar seu projeto no Console de APIs do Google, você seleciona entre diferentes credenciais, dependendo do fluxo que está usando. Para mais detalhes, consulte Como configurar o OAuth 2.0 e Cenários do OAuth 2.0. Confira abaixo os snippets de código para cada um dos fluxos.

Aplicativos do servidor da Web

O protocolo para esse fluxo é explicado em Como usar o OAuth 2.0 para aplicativos de servidor da Web.

Ela fornece classes auxiliares de webinar para simplificar significativamente o fluxo do código de autorização para casos de uso básicos. Basta fornecer subclasses concretas de AbstractAuthorizationCodeServlet e AbstractAuthorizationCodeCallbackServlet (de google-oauth-client-servlet) e adicioná-las ao seu arquivo web.xml. Observe que você ainda precisa cuidar do login do usuário do seu aplicativo da Web e extrair um ID do usuário.

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

Aplicativos do Google App Engine

O fluxo do código de autorização no App Engine é quase idêntico ao fluxo de código de autorização do json, exceto pelo fato de que podemos aproveitar a API User Java do Google App Engine. O usuário precisa estar conectado para que a API User Java seja ativada. Para informações sobre como redirecionar usuários para uma página de login, caso eles ainda não estejam conectados, consulte Segurança e autenticação (em web.xml).

A principal diferença do caso do inclui é que você fornece subclasses concretas de AbstractAppEngineAuthorizationCodeServlet e AbstractAppEngineAuthorizationCodeCallbackServlet (de google-oauth-client-appengine. Elas estendem as classes de json abstratas e implementam o método getUserId usando a API User Java. O AppEngineDataStoreFactory (de google-http-client-appengine) é uma boa opção para manter a credencial usando a API Data Store do Google App Engine.

Exemplo retirado (ligeiramente 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 um exemplo adicional, consulte storage-serviceaccount-appengine-sample.

Contas de serviço

O GoogleCredential também oferece suporte a contas de serviço. Ao contrário da credencial em que um aplicativo cliente solicita acesso aos dados de um usuário final, as contas de serviço dão acesso aos próprios dados do aplicativo cliente. O aplicativo cliente assina a solicitação de token de acesso usando uma chave privada salva no Console de APIs do Google.

Exemplo de código retirado 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 ver outro exemplo, consulte storage-serviceaccount-cmdline-sample.

Falsificação de identidade

Também é possível usar o fluxo de conta de serviço para representar um usuário em um domínio de sua propriedade. Isso é muito semelhante ao fluxo da conta de serviço acima, mas você também chama GoogleCredential.Builder.setServiceAccountUser(String).

Apps instalados

Esse é o fluxo do código de autorização da linha de comando descrito em Como usar o OAuth 2.0 para aplicativos instalados.

Exemplo de snippet 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");
}

Aplicativos do lado do cliente

Para usar o fluxo de cliente baseado em navegador descrito em Como usar o OAuth 2.0 para aplicativos do lado do cliente, normalmente você precisa seguir estas etapas:

  1. Redirecione o usuário final no navegador para a página de autorização usando GoogleBrowserClientRequestUrl para conceder ao seu aplicativo de navegador acesso aos dados protegidos do usuário final.
  2. Use a biblioteca de cliente das APIs do Google para JavaScript para processar o token de acesso encontrado no fragmento de URL, no URI de redirecionamento registrado no Console de APIs do Google.

Exemplo de uso para um aplicativo da 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

Qual biblioteca usar com o Android:

Se você estiver desenvolvendo para Android e a API do Google que você quer usar estiver incluída na biblioteca do Google Play Services, use essa biblioteca para ter o melhor desempenho e a melhor experiência. Se a API do Google que você quer usar com o Android não faz parte da biblioteca do Google Play Services, é possível usar a biblioteca de cliente de APIs do Google para Java, que é compatível com o Android 4.0 (Ice Cream Sandwich) (ou mais recente) e é descrita aqui. A compatibilidade com Android na biblioteca de cliente de APIs do Google para Java é @Beta.

Histórico

A partir do Eclair (SDK 2.1), as contas de usuário são gerenciadas em um dispositivo Android usando o gerente de contas. Toda a autorização de apps Android é gerenciada centralmente pelo SDK usando o AccountManager. Você especifica o escopo do OAuth 2.0 necessário para seu aplicativo e ele retorna um token de acesso para ser usado.

O escopo do OAuth 2.0 é especificado pelo parâmetro authTokenType como oauth2:, mais o escopo. Exemplo:

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

Especifica o acesso de leitura/gravação à API Google Tasks. Se você precisar de vários escopos do OAuth 2.0, use uma lista separada por espaços.

Algumas APIs têm parâmetros authTokenType especiais que também funcionam. Por exemplo, "Gerenciar suas tarefas" é um alias para o exemplo de authtokenType mostrado acima.

Você também precisa especificar a chave de API no Console de APIs do Google. Caso contrário, o token fornecido pelo AccountManager fornece apenas uma cota anônima, que geralmente é muito baixa. Por outro lado, ao especificar uma chave de API, você recebe uma cota sem custo financeiro maior e, opcionalmente, pode configurar o faturamento para uso acima dessa cota.

Exemplo de snippet de código extraído 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;
  }
}