Informações gerais
Objetivo: este documento descreve as funções genéricas do OAuth 2.0 oferecidas pela biblioteca de cliente do OAuth do Google para Java. É possível usar essas funções para autenticação e autorização de qualquer serviço da Internet.
Para instruções sobre como usar o GoogleCredential
para fazer a autorização do OAuth 2.0 com
os 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 para permitir que os usuários finais autorizem com segurança um aplicativo cliente a acessar recursos protegidos do lado do servidor. Além disso, a especificação do token do portador OAuth 2.0 explica como acessar esses recursos protegidos usando um token de acesso concedido durante o processo de autorização do usuário final.
Para mais detalhes, consulte a documentação do Javadoc para os seguintes pacotes:
- com.google.api.client.auth.oauth2 (de google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (from google-oauth-client-servlet)
- com.google.api.client.extensions.appengine.auth.oauth2 (from google-oauth-client-appengine)
Registro do cliente
Antes de usar a biblioteca de cliente do Google OAuth para Java, é provável que você precise registrar o aplicativo em um servidor de autorização para receber um ID e uma chave secreta do cliente. Para informações gerais sobre esse processo, consulte a especificação do registro do cliente.
Credencial e armazenamento de credenciais
Credencial
é uma classe auxiliar OAuth 2.0 segura para linhas de execução que serve para acessar recursos protegidos usando um
token de acesso. Ao usar um token de atualização, Credential
também atualiza o token de acesso quando o token de acesso expira usando o token de atualização. Por exemplo, se você já tiver um token de acesso, poderá fazer uma solicitação desta 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 o token de atualização para evitar um redirecionamento futuro para a página de autorização no navegador. A implementação de CredentialStore dessa biblioteca foi descontinuada e será removida em versões futuras. A alternativa é usar as interfaces 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:
- JdoDataStoreFactory mantém a credencial usando aJ-J.
- AppEngineDataStoreFactory mantém a credencial usando a API Data Store do Google App Engine.
- MemoryDataStoreFactory "mantém" a credencial na memória, o que é útil apenas como um armazenamento de curto prazo durante a vida útil do processo.
- FileDataStoreFactory mantém a credencial em um arquivo.
Usuários do Google App Engine:
O uso de AppEngineCredentialStore foi descontinuado e está sendo removido.
Recomendamos que você use o AppEngineDataStoreFactory com StoredCredential. Se você tiver credenciais armazenadas da maneira antiga, use os métodos auxiliares MigrateTo(AppEngineDataStoreFactory) ou MigrateTo(DataStore) adicionados para migrar.
Use DataStoreCredentialRefreshListener e defina-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 dados protegidos. O protocolo para esse fluxo é definido na especificação de concessão de código de autorização.
Esse fluxo é implementado usando AuthorizationCodeFlow. Essas etapas são:
- Um usuário final faz login no seu aplicativo. Você precisa associar esse usuário a um ID exclusivo para o aplicativo.
- Chame AuthorizationCodeFlow.loadCredential(String), com base no ID do usuário, para verificar se as credenciais dele já são conhecidas. Se sim, está tudo pronto.
- Caso contrário, chame AuthorizationCodeFlow.newAuthorizationUrl() e direcione o navegador do usuário final para uma página de autorização em que ele possa conceder ao aplicativo acesso aos dados protegidos.
- O navegador da Web redireciona para o URL de redirecionamento com um parâmetro de consulta "código", que pode ser usado para solicitar um token de acesso com AuthorizationCodeFlow.newTokenRequest(String).
- Use AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) para armazenar e receber uma credencial para acessar recursos protegidos.
Como alternativa, se você não estiver usando AuthorizationCodeFlow, poderá usar as classes de nível inferior:
- Use DataStore.get(String) para carregar a credencial do repositório, com base no ID do usuário.
- Use AuthorizationCodeRequestUrl para direcionar o navegador para a página de autorização.
- Use AuthorizationCodeResponseUrl para processar a resposta de autorização e analisar o código de autorização.
- Use AuthorizationCodeTokenRequest para solicitar um token de acesso e possivelmente um de atualização.
- Crie uma nova Credential e armazene-a usando DataStore.set(String, V).
- Acesse recursos protegidos usando a credencial. Os tokens de acesso expirados são atualizados automaticamente com o token de atualização, se aplicável. Use o DataStoreCredentialRefreshListener e o defina para a credencial com Credential.Builder.addRefreshListener(CredentialRefreshListener).
Fluxo do código de autorização do RecyclerView
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.
Exemplo de código:
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 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 saber como redirecionar os usuários a 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 webinar é que você fornece subclasses concretas de AbstractAppEngineAuthorizationCodeServlet e AbstractAppEngineAuthorizationCodeCallbackServlet (de google-oauth-client-appengine). Elas estendem as classes de waypoint abstratas e implementam o método getUserId
para você usando a API User Java. AppEngineDataStoreFactory (da biblioteca de cliente HTTP do Google para Java) é uma boa opção para manter a credencial usando a API Google App Engine Data Store.
Exemplo de código:
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 em navegador
Estas são as etapas típicas do fluxo de cliente baseado no navegador especificado na especificação de concessão implícita:
- Usando BrowserClientRequestUrl, redirecione o navegador do usuário final para a página de autorização em que o usuário final pode conceder ao seu aplicativo acesso aos dados protegidos.
- Use um aplicativo JavaScript para processar o token de acesso encontrado no fragmento de URL no URI de redirecionamento 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 token de acesso
expirado, o servidor normalmente responde com um código de status HTTP 401 Unauthorized
como este:
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 de OAuth 2.0.
Outra abordagem possível é verificar o parâmetro expires_in
na resposta do token de acesso.
Isso especifica a vida útil em segundos do token de acesso concedido, que normalmente é uma hora. No entanto, o token de acesso pode não expirar realmente no final desse período e o servidor pode continuar permitindo o acesso. É por isso que normalmente
recomendamos aguardar um código de status 401 Unauthorized
, em vez de
presumir que o token expirou com base no tempo decorrido. Como alternativa, tente atualizar um token de acesso pouco antes de ele expirar e, se o servidor de token estiver indisponível, continue usando o token de acesso até receber um 401
. Essa
é a estratégia usada por padrão em
Credential.
Outra opção é pegar um novo token de acesso antes de cada solicitação. Porém, isso exige sempre uma solicitação HTTP extra para o servidor de token, então é provável que essa seja uma opção ruim em termos de velocidade e uso da rede. O ideal é armazenar o token de acesso em um armazenamento seguro e permanente para minimizar as solicitações de novos tokens de acesso a um aplicativo. No entanto, 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, verifique se o código de tratamento de erros é robusto. Depois de detectar que um token não é mais válido (por exemplo, se ele expirou ou foi revogado), remova o token de acesso do armazenamento. No Android, por exemplo, é preciso chamar AccountManager.invalidateAuthToken.