OAuth 2.0 e a biblioteca de cliente OAuth do Google para Java

Visão geral

Finalidade: este documento descreve as funções genéricas do OAuth 2.0 oferecidas pelo a biblioteca de cliente OAuth do Google para Java. Você pode usar essas funções para e autorização para quaisquer serviços de Internet.

Para instruções sobre como usar o GoogleCredential para autorizar o OAuth 2.0 com Serviços do Google. Consulte Como usar o OAuth 2.0 com a biblioteca de cliente das APIs do Google para Java.

Resumo: o OAuth 2.0 é uma especificação padrão que permita que os usuários finais autorizem um cliente com segurança para acessar recursos protegidos do lado do servidor. Além disso, Token do portador OAuth 2.0 explica como acessar esses recursos protegidos usando uma interface concedido durante o processo de autorização do usuário final.

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

Registro do cliente

Antes de usar a biblioteca de cliente OAuth do Google para Java, você provavelmente precisará registrar seu aplicativo com um servidor de autorização para receber um ID do cliente e chave secreta do cliente. (Para obter informações gerais sobre esse processo, consulte a Cliente Especificação de registro.)

Armazenamento de credenciais e credenciais

Credencial é uma classe auxiliar OAuth 2.0 segura para linhas de execução para acessar recursos protegidos usando uma token de acesso. Ao usar um token de atualização, o Credential também atualiza o acesso. token de acesso quando o token de acesso expirar usando o token de atualização. Por exemplo, se você já tem um token de acesso, é possível fazer uma solicitação da seguinte maneira:

  public static HttpResponse executeGet(
      HttpTransport transport, JsonFactory jsonFactory, String accessToken, GenericUrl url)
      throws IOException {
    Credential credential =
        new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(accessToken);
    HttpRequestFactory requestFactory = transport.createRequestFactory(credential);
    return requestFactory.buildGetRequest(url).execute();
  }

A maioria dos aplicativos precisa manter o token de acesso da credencial e token de atualização para evitar um redirecionamento futuro para a autorização no navegador. A CredentialStore A implementação nesta biblioteca foi descontinuada e será removida no futuro lançamentos. A alternativa é usar DataStoreFactory e DataStore com StoredCredential, que são fornecidas pela Biblioteca de cliente HTTP do Google para Java.

Você pode usar uma das seguintes implementações fornecidas pela biblioteca:

Usuários do Google App Engine:

O uso de AppEngineCredentialStore foi descontinuado e está sendo removido.

Recomendamos que você use AppEngineDataStoreFactory com StoredCredential. Se as credenciais estiverem armazenadas da forma antiga, use os métodos auxiliares adicionados migrateTo(AppEngineDataStoreFactory) ou migrateTo(DataStore) para a migração.

Use DataStoreCredentialRefreshListener. e defini-lo 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 a seu aplicativo acesso aos dados protegidos. O protocolo desse fluxo é especificado na Especificação da concessão do código de autorização.

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

Como alternativa, se você não usa AuthorizationCodeFlow. você pode usar as classes de nível inferior:

Fluxo do código de autorização do servlet

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.

Código de exemplo:

public class ServletSample 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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

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

public class ServletCallbackSample 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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

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

Fluxo do código de autorização 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 Users Java seja ativada; para informações sobre como redirecionar os usuários a uma página de login, caso ainda não tenham feito tiver 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ê usando a API Users do Java. O AppEngineDataStoreFactory (da biblioteca de cliente HTTP do Google para Java) é uma boa opção para manter a credencial usando a API Data Store do Google App Engine.

Código de exemplo:

public class AppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

public class AppEngineCallbackSample extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  @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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

Fluxo do código de autorização de linha de comando

Exemplo de código simplificado retirado de dailymotion-cmdline-sample:

/** Authorizes the installed application to access user's protected data. */
private static Credential authorize() throws Exception {
  OAuth2ClientCredentials.errorIfNotSpecified();
  // set up authorization code flow
  AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken
      .authorizationHeaderAccessMethod(),
      HTTP_TRANSPORT,
      JSON_FACTORY,
      new GenericUrl(TOKEN_SERVER_URL),
      new ClientParametersAuthentication(
          OAuth2ClientCredentials.API_KEY, OAuth2ClientCredentials.API_SECRET),
      OAuth2ClientCredentials.API_KEY,
      AUTHORIZATION_SERVER_URL).setScopes(Arrays.asList(SCOPE))
      .setDataStoreFactory(DATA_STORE_FACTORY).build();
  // authorize
  LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost(
      OAuth2ClientCredentials.DOMAIN).setPort(OAuth2ClientCredentials.PORT).build();
  return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}

private static void run(HttpRequestFactory requestFactory) throws IOException {
  DailyMotionUrl url = new DailyMotionUrl("https://api.dailymotion.com/videos/favorites");
  url.setFields("id,tags,title,url");

  HttpRequest request = requestFactory.buildGetRequest(url);
  VideoFeed videoFeed = request.execute().parseAs(VideoFeed.class);
  ...
}

public static void main(String[] args) {
  ...
  DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
  final Credential credential = authorize();
  HttpRequestFactory requestFactory =
      HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
        @Override
        public void initialize(HttpRequest request) throws IOException {
          credential.initialize(request);
          request.setParser(new JsonObjectParser(JSON_FACTORY));
        }
      });
  run(requestFactory);
  ...
}

Fluxo de cliente baseado no navegador

Estas são as etapas típicas do fluxo de cliente baseado em navegador especificado na Especificação de concessão implícita:

  • Usando BrowserClientRequestUrl, redirecionam o navegador do usuário final para a página de autorização onde o usuário final pode conceda ao seu aplicativo acesso aos dados protegidos.
  • Use um aplicativo JavaScript para processar o token de acesso encontrado no URL no URI de redirecionamento que está registrado no servidor de autorização.

Exemplo de uso para um aplicativo da Web:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  String url = new BrowserClientRequestUrl(
      "https://server.example.com/authorize", "s6BhdRkqt3").setState("xyz")
      .setRedirectUri("https://client.example.com/cb").build();
  response.sendRedirect(url);
}

Como detectar um token de acesso expirado

De acordo com a especificação do portador OAuth 2.0, quando o servidor é chamado para acessar um recurso protegido com um acesso expirado. token, o servidor normalmente responde com um código de status HTTP 401 Unauthorized como o seguinte:

   HTTP/1.1 401 Unauthorized
   WWW-Authenticate: Bearer realm="example",
                     error="invalid_token",
                     error_description="The access token expired"

No entanto, parece haver muita flexibilidade na especificação. Para detalhes, consulte a documentação do provedor OAuth 2.0.

Uma abordagem alternativa é verificar o parâmetro expires_in no resposta do token de acesso. Especifica a vida útil em segundos do token de acesso concedido, que é geralmente uma hora. No entanto, é possível que o token de acesso não expire durante esse período, e o servidor pode continuar permitindo o acesso. É por isso que nós normalmente recomendamos aguardar um código de status 401 Unauthorized, em vez supondo que o token tenha expirado com base no tempo decorrido. Também é possível tentar atualizar o token de acesso pouco antes de expirar. Se o servidor de token estiver indisponível, continue usando o token de acesso até receber um 401. Isso é a estratégia usada por padrão Credencial.

Outra opção é obter um novo token de acesso antes de cada solicitação, mas isso exige sempre uma solicitação HTTP extra para o servidor de token, então é provável que má escolha em termos de velocidade e uso da rede. O ideal é armazenar o token de acesso no armazenamento seguro e persistente para minimizar as solicitações de um aplicativo por novos tokens. Porém, para aplicativos instalados, o armazenamento seguro é um problema difícil.

Um token de acesso pode se tornar inválido por outros motivos além da expiração, por exemplo, se o usuário revogou o token explicitamente. Portanto, o código de tratamento de erros é robusto. Quando você detectar que um token não está mais válido, por exemplo, se ele tiver expirado ou sido revogado, você precisará remover o acesso token do seu armazenamento. No Android, por exemplo, você precisa chamar AccountManager.invalidateAuthToken.