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

Visão geral

Finalidade: este documento explica como usar o GoogleCredential para autorizar o OAuth 2.0 com os serviços do Google. Para 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 OAuth 2.0 para autorização. As APIs do Google oferecem suporte aos fluxos do OAuth 2.0 para diferentes tipos de aplicativos cliente. Em todos esses fluxos, o aplicativo cliente solicita um token de acesso que é associadas somente ao seu aplicativo cliente e ao proprietário dos dados protegidos que está sendo acessado. O token de acesso também está associado a um escopo limitado define o tipo de dados aos quais seu aplicativo cliente tem acesso (por exemplo, "Gerenciar suas tarefas"). Uma meta importante do OAuth 2.0 é fornecer acesso conveniente aos dados protegidos, minimizando o possível impacto se um token de acesso for roubado.

Os pacotes OAuth 2.0 na biblioteca de cliente de APIs do Google para Java são de uso geral Biblioteca de cliente do Google OAuth 2.0 para Java.

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

Console de APIs do Google

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

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

Credencial

GoogleCredential

GoogleCredential é uma classe auxiliar thread-safe para OAuth 2.0 para acesso a recursos protegidos usando um token de acesso. Por exemplo, se você já tem um token de acesso, podem fazer uma solicitação da seguinte 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 no API Java da identidade do aplicativo do Google App Engine. Diferentemente da credencial em que um aplicativo cliente solicita acesso a um dados do usuário final, a API App Identity fornece acesso ao cliente do próprio aplicativo.

Usar AppIdentityCredential (de google-api-client-appengine). Essa credencial é muito mais simples porque o Google App Engine cuida de todos os detalhes. Você só especifica 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

Normalmente, o token de acesso tem uma data de validade de 1 hora, após a qual você um erro será exibido se você tentar usá-lo. GoogleCredential cuida da "atualização" automática do token, o que significa simplesmente receber um novo token de acesso. Isso é feito por meio de um token de atualização de longa duração, que normalmente é recebido junto com o token de acesso, se você usar o 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 da credencial e/ou token de atualização. Para manter os tokens de acesso e/ou atualização da credencial, você pode fornecer sua própria implementação do DataStoreFactory com StoredCredential ou você pode usar uma das seguintes implementações fornecidas pela biblioteca:

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

Você pode usar DataStoreCredentialRefreshListener e configure-o para a credencial usando 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 seus dados protegidos nas APIs do Google. O protocolo para esse fluxo é especificado em Concessão do código de autorização.

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

Como alternativa, se você não estiver usando GoogleAuthorizationCodeFlow, use 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. Abaixo estão os snippets de código de cada um dos fluxos.

Aplicativos do servidor da Web

O protocolo desse fluxo é explicado em Usar o OAuth 2.0 para aplicativos de servidor da Web

Essa biblioteca fornece classes auxiliares de servlet para simplificar significativamente a fluxo do código de autorização para casos de uso básicos. Você acabou de fornecer subclasses concretas de AbstractAuthorizationCodeServlet e AbstractAuthorizationCodeCallbackServlet. (de google-oauth-client-servlet) e adicione-as ao arquivo web.xml. Observe que você ainda precisa cuidar do gerenciamento login no seu aplicativo da Web e extrair um ID de 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 do servlet fluxo do código de autorização, exceto que podemos aproveitar API Users Java. O usuário precisa estar conectado para que a API de usuários Java seja ativada; para informações sobre redirecionando usuários para uma página de login se eles ainda não tiverem feito login, consulte Segurança e autenticação (em web.xml).

A principal diferença dos servlets é que você fornece instruções subclasses de AbstractAppEngineAuthorizationCodeServlet e AbstractAppEngineAuthorizationCodeCallbackServlet (de google-oauth-client-appengine. Eles estendem as classes de servlet abstratas e implementam o método getUserId. para você usar a API de usuários Java. AppEngineDataStoreFactory (de google-http-client-appengine) é uma boa opção para manter a credencial usando a API de dados Store.

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

Contas de serviço

GoogleCredential também oferece suporte a contas de serviço. Diferentemente da credencial em que um aplicativo cliente solicita acesso a um dados do usuário final, as contas de serviço fornecem acesso ao sistema próprios dados. Seu aplicativo cliente assina a solicitação de um token de acesso usando uma chave privada baixada do 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 obter um exemplo adicional, consulte storage-serviceaccount-cmdline-sample.

Falsificação de identidade

Você também pode usar o fluxo da conta de serviço para representar um usuário em um domínio que que você possui. Isso é muito semelhante ao fluxo da conta de serviço acima, mas você chame também GoogleCredential.Builder.setServiceAccountUser(String).

Aplicativos instalados

Este é 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 no navegador descrito em Usar o OAuth 2.0 para aplicativos do lado do cliente, você normalmente seguiria estas etapas:

  1. Redirecione o usuário final no navegador para a página de autorização usando GoogleBrowserClientRequestUrl para conceder ao seu navegador acesso aos dados protegidos do usuário final.
  2. Usar 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 registrada 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 desejada estiver incluída no Biblioteca do Google Play Services, e usar essa biblioteca para ter o melhor desempenho e experiência. Se a API do Google que você deseja usar com o Android não faz parte da biblioteca do Google Play Services, pode usar a biblioteca de cliente da API do Google para Java, compatível com o Android 4.0 (Ice Cream Sandwich) (ou superior) e que está descrito aqui. A compatibilidade com o Android no Google A biblioteca de cliente da API 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 Gerenciador de contas. Toda a autorização de apps Android é centralizada gerenciados pelo SDK usando AccountManager. Você especifica o escopo do OAuth 2.0 de que seu aplicativo precisa e ele retorna uma solicitação token a ser usado.

O escopo do OAuth 2.0 é especificado pelo parâmetro authTokenType como oauth2:. além do escopo. Exemplo:

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

Isso especifica o acesso de leitura/gravação à API Google Tasks. Se você precisar de várias escopos OAuth 2.0, usem uma lista separada por espaços.

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

Também é necessário especificar a chave de API do Console de APIs do Google. Caso contrário, o token fornecido pelo gerente da conta fornece apenas os anônima, que geralmente é muito baixa. Por outro lado, ao especificar uma API você recebe uma cota sem custo financeiro maior e pode configurar o faturamento para uso acima disso.

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