Para acessar um serviço on-line com segurança, os usuários precisam se autenticar no serviço. Eles precisam fornecer um comprovante de identidade. Para um aplicativo que acessa um serviço de terceiros, o problema de segurança é ainda mais complicado. O usuário não só precisa de autenticação para acessar o serviço, como também o aplicativo precisa ter autorização para agir em nome do usuário.
A forma padrão do setor de lidar com autenticação para serviços de terceiros é o protocolo OAuth2. O OAuth2 oferece um valor único, chamado token de autenticação, que representa a identidade do usuário e a autorização do aplicativo para agir em nome dele. Esta lição mostra como se conectar a um servidor do Google compatível com o OAuth2. Os serviços do Google são usados como exemplo, mas as técnicas demonstradas funcionam em qualquer serviço que ofereça suporte ao protocolo OAuth2.
Usar o OAuth2 é bom para:
- Conseguir 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 conhecer alguns detalhes específicos da API sobre o serviço 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 solicitado pelo app. 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 e gravação ao Google Tarefas éManage your tasks
. - Um ID e uma chave secreta do cliente, que são strings que identificam seu app para o serviço. Você precisa receber essas strings diretamente do proprietário do serviço. O Google tem um sistema de autoatendimento para receber IDs e secrets do cliente.
Solicitar a permissão de acesso à Internet
Para apps destinados ao Android 6.0 (nível 23 da API) e versões mais recentes, o
método getAuthToken()
não requer nenhuma permissão. No entanto, para
realizar operações no token, você precisa adicionar a permissão
INTERNET
ao arquivo de manifesto, conforme mostrado no snippet de código a seguir:
<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
é assíncrono. Isso significa que, em vez de fazer todo o trabalho de autenticação
em uma única função, você precisa implementá-lo 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
AccountManagerCallback
. AccountManager
chama
run()
em OnTokenAcquired
com um
AccountManagerFuture
que contém um Bundle
. Se
a chamada for bem-sucedida, o token estará 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
conterá um token válido na chave KEY_AUTHTOKEN
e está tudo pronto.
Sua primeira solicitação de um 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 processar os dois primeiros casos de maneira trivial, geralmente apenas mostrando uma mensagem de erro ao usuário. Se a rede estiver inativa ou o usuário tiver decidido não conceder acesso, não haverá muito que seu aplicativo possa fazer a respeito. Os dois últimos casos são um pouco mais complicados, porque espera-se que aplicativos bem comportados processem essas falhas automaticamente.
O terceiro caso de falha, ter credenciais insuficientes, é comunicado pelo
Bundle
que você recebe no seu AccountManagerCallback
(OnTokenAcquired
do exemplo anterior). Se o Bundle
incluir
um Intent
na chave KEY_INTENT
,
o autenticador estará informando que precisa interagir diretamente com o usuário antes de poder
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 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 obtê-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()
, para que você possa capturar
o resultado do Intent
implementando onActivityResult()
na
sua própria atividade. Isso é importante: se você não capturar o resultado da
Intent
de resposta do autenticador,
será impossível dizer se o usuário foi autenticado.
Se o resultado for RESULT_OK
, o
autenticador atualizou as credenciais armazenadas para que elas sejam suficientes para
o nível de acesso solicitado. Chame AccountManager.getAuthToken()
novamente para solicitar o novo
token de autenticação.
O último caso, em que o token expirou, não é uma falha de AccountManager
. A única maneira de descobrir se um token expirou é entrar em contato com o servidor. Seria
um desperdício e caro para 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 pequenos ajustes nessas instruções para considerar sua situação específica.
As APIs do 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 do 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);
Caso a solicitação retorne um código de erro HTTP 401, isso significa que seu token foi negado. Conforme mencionado na última seção, o motivo mais comum para isso é a expiração do token. A correção é simples: chame AccountManager.invalidateAuthToken()
e repita o processo de aquisição do token mais uma vez.
Como os tokens expirados são uma ocorrência comum e a correção deles é muito fácil, muitos aplicativos simplesmente supõem que o token expirou antes mesmo de solicitá-lo. Se a renovação de um token for uma
operação barata para o servidor, pode ser preferível chamar
AccountManager.invalidateAuthToken()
antes da primeira chamada para
AccountManager.getAuthToken()
e evitar a necessidade de solicitar um token de autenticação
duas vezes.