
Para acessar um serviço on-line com segurança, os usuários precisam passar por autenticação. Eles têm que fornecer uma comprovação de identidade. Para apps que acessam serviços de terceiros, o problema de segurança é ainda mais complicado. O usuário precisa não só ser autenticado para acessar o serviço, como também o app precisa estar autorizado a agir em nome do usuário.
A maneira padrão do setor de lidar com autenticação para serviços de terceiros é o protocolo OAuth2. O OAuth2 fornece um valor único, chamado de token de autenticação, que representa tanto a identidade do usuário quanto a autorização do aplicativo para agir em nome do usuário. Esta lição mostra como se conectar a um servidor do Google compatível com o OAuth2. Embora os serviços do Google sejam usados como exemplo, as técnicas demonstradas funcionarão em qualquer serviço que ofereça compatibilidade com o protocolo OAuth2.
Usar o OAuth2 é bom para:
- receber permissão do usuário para acessar um serviço on-line usando a conta dele;
- autenticar um serviço on-line em nome do usuário;
- lidar com erros de autenticação.
Coletar informações
Para começar a usar o OAuth2, você precisa saber algumas informações sobre a API que está tentando acessar:
- O URL do serviço que você quer acessar.
- O escopo de autenticação, que é uma string que define o tipo específico de acesso que seu app está pedindo. Por exemplo, o escopo de autenticação para acesso somente leitura ao Google Tarefas é
View your tasks
, enquanto o escopo de autenticação para acesso de leitura/gravação ao Google Tarefas éManage your tasks
. - Um ID do cliente e uma chave secreta do cliente, que são strings que identificam seu app para o serviço. É necessário receber essas strings diretamente do proprietário do serviço. O Google tem um sistema de autoatendimento para receber IDs e chaves secretas do cliente. O artigo Autorização ou uso de APIs REST explica como usar esse sistema para receber esses valores e usá-los com a API Google Tasks.
Solicitar a permissão de acesso à Internet
Para apps voltados ao Android 6.0 (API de nível 23) e versões mais recentes, o método getAuthToken()
não requer nenhuma permissão. Para realizar operações no token, no entanto, você precisa adicionar a permissão INTERNET
ao seu arquivo de manifesto, como mostrado no seguinte snippet de código:
<manifest ... > <uses-permission android:name="android.permission.INTERNET" /> ... </manifest>
Solicitar um token de autenticação
Para receber o token, chame AccountManager.getAuthToken()
.
Cuidado: como algumas operações de conta podem envolver a comunicação de rede, a maioria dos métodos AccountManager
são assíncronos. Isso significa que, em vez de fazer toda sua autenticação funcionar em uma função, você precisa implementá-la como uma série de callbacks.
O snippet a seguir mostra como trabalhar com uma série de callbacks para receber o token:
Kotlin
val am: AccountManager = AccountManager.get(this) val options = Bundle() am.getAuthToken( myAccount_, // Account retrieved using getAccountsByType() "Manage your tasks", // Auth scope options, // Authenticator-specific options this, // Your activity OnTokenAcquired(), // Callback called when a token is successfully acquired Handler(OnError()) // Callback called if an error occurs )
Java
AccountManager am = AccountManager.get(this); Bundle options = new Bundle(); am.getAuthToken( myAccount_, // Account retrieved using getAccountsByType() "Manage your tasks", // Auth scope options, // Authenticator-specific options this, // Your activity new OnTokenAcquired(), // Callback called when a token is successfully acquired new Handler(new OnError())); // Callback called if an error occurs
Neste exemplo, OnTokenAcquired
é uma classe que implementa o AccountManagerCallback
. AccountManager
chama run()
em OnTokenAcquired
com um AccountManagerFuture
que contém um Bundle
. Se a chamada é concluída, o token está dentro do Bundle
.
Veja como conseguir o token do Bundle
:
Kotlin
private class OnTokenAcquired : AccountManagerCallback<Bundle> { override fun run(result: AccountManagerFuture<Bundle>) { // Get the result of the operation from the AccountManagerFuture. val bundle: Bundle = result.getResult() // The token is a named value in the bundle. The name of the value // is stored in the constant AccountManager.KEY_AUTHTOKEN. val token: String = bundle.getString(AccountManager.KEY_AUTHTOKEN) } }
Java
private class OnTokenAcquired implements AccountManagerCallback<Bundle> { @Override public void run(AccountManagerFuture<Bundle> result) { // Get the result of the operation from the AccountManagerFuture. Bundle bundle = result.getResult(); // The token is a named value in the bundle. The name of the value // is stored in the constant AccountManager.KEY_AUTHTOKEN. String token = bundle.getString(AccountManager.KEY_AUTHTOKEN); ... } }
Se tudo correr bem, o Bundle
terá um token válido na chave KEY_AUTHTOKEN
e estará tudo pronto para você continuar. No entanto, nem sempre é tão simples…
Solicitar um token de autenticação… de novo
Sua primeira solicitação de token de autenticação pode falhar por vários motivos:
- Um erro no dispositivo ou na rede causado a falha do
AccountManager
. - O usuário decidiu não conceder ao seu app acesso à conta.
- As credenciais da conta armazenadas não são suficientes para ter acesso a ela.
- O token de autenticação armazenado em cache expirou.
Os aplicativos podem lidar com os dois primeiros casos de forma trivial, geralmente exibindo uma mensagem de erro para o usuário. Se a rede estiver inativa ou o usuário decidir não conceder acesso, não haverá muita coisa que seu aplicativo possa fazer a respeito. Os dois últimos casos são um pouco mais complicados, porque espera-se que os aplicativos com um bom comportamento lidem com essas falhas automaticamente.
O terceiro caso de falha (credenciais insuficientes) é comunicado por meio do Bundle
que você recebe no seu AccountManagerCallback
(OnTokenAcquired
do exemplo anterior). Se o Bundle
inclui um Intent
na chave KEY_INTENT
, o autenticador está dizendo que precisa interagir diretamente com o usuário antes que ele possa fornecer um token válido.
Pode haver muitas razões para o autenticador retornar um Intent
. Talvez seja a primeira vez que o usuário fez login nessa conta. Talvez a conta do usuário tenha expirado e ele precise fazer login novamente, ou as credenciais armazenadas estejam incorretas. Talvez a conta exija uma autenticação de dois fatores ou precise ativar a câmera para fazer uma verificação de retina. Não importa qual seja o motivo. Se você quiser um token válido, será necessário disparar o Intent
para recebê-lo.
Kotlin
private inner class OnTokenAcquired : AccountManagerCallback<Bundle> { override fun run(result: AccountManagerFuture<Bundle>) { val launch: Intent? = result.getResult().get(AccountManager.KEY_INTENT) as? Intent if (launch != null) { startActivityForResult(launch, 0) } } }
Java
private class OnTokenAcquired implements AccountManagerCallback<Bundle> { @Override public void run(AccountManagerFuture<Bundle> result) { ... Intent launch = (Intent) result.getResult().get(AccountManager.KEY_INTENT); if (launch != null) { startActivityForResult(launch, 0); return; } } }
Observe que o exemplo usa startActivityForResult()
, então você pode capturar o resultado do Intent
por meio da implementação de onActivityResult()
na sua atividade. Isso é importante. Se você não capturar o resultado do Intent
de resposta do autenticador, será impossível dizer se o usuário foi autenticado ou não.
Se o resultado for RESULT_OK
, o autenticador atualizou as credenciais armazenadas de modo que elas sejam suficientes para o nível de acesso que você solicitou. Chame AccountManager.getAuthToken()
novamente para solicitar o novo token de autenticação.
O último caso, em que o token expirou, não é uma falha do AccountManager
. A única maneira de descobrir se um token expirou ou não é entrar em contato com o servidor, e seria um desperdício para o AccountManager
ficar sempre on-line para verificar o estado de todos os tokens.
Portanto, essa é uma falha que só pode ser detectada quando um aplicativo como o seu tenta usar o token de autenticação para acessar um serviço on-line.
Conectar ao serviço on-line
O exemplo abaixo mostra como se conectar a um servidor do Google. Como o Google usa o protocolo OAuth2 padrão do setor para autenticar solicitações, as técnicas discutidas aqui são amplamente aplicáveis. No entanto, lembre-se de que cada servidor é diferente. Talvez você precise fazer pequenas modificações a essas instruções para considerar sua situação específica.
As APIs Google exigem que você forneça quatro valores a cada solicitação: a chave de API, o ID do cliente, a chave secreta do cliente e a chave de autenticação. Os três primeiros vêm do site Console de APIs do Google. O último é o valor de string que você recebeu chamando AccountManager.getAuthToken()
. Você os transmite para o servidor do Google como parte de uma solicitação HTTP.
Kotlin
val url = URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=$your_api_key") val conn = url.openConnection() as HttpURLConnection conn.apply { addRequestProperty("client_id", your client id) addRequestProperty("client_secret", your client secret) setRequestProperty("Authorization", "OAuth $token") }
Java
URL url = new URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=" + your_api_key); URLConnection conn = (HttpURLConnection) url.openConnection(); conn.addRequestProperty("client_id", your client id); conn.addRequestProperty("client_secret", your client secret); conn.setRequestProperty("Authorization", "OAuth " + token);
Se a solicitação retornar um código de erro HTTP 401, seu token foi negado. Conforme mencionado na última seção, o motivo mais comum para isso é que o token expirou. A solução é simples: chame AccountManager.invalidateAuthToken()
e repita o procedimento de aquisição do token mais uma vez.
Como os tokens expirados são uma ocorrência comum e corrigi-los é muito fácil, vários aplicativos simplesmente supõem que o token expirou antes mesmo de solicitá-lo. Se a renovar um token for uma operação de baixo custo para seu servidor, você pode preferir chamar AccountManager.invalidateAuthToken()
antes da primeira chamada para AccountManager.getAuthToken()
e se livrar da necessidade de solicitar um token de autenticação duas vezes.