Android
Integração nativa Android para o Surt Guardian SDK, distribuída via Maven (GitHub Packages). O SDK coleta sinais do dispositivo, executa verificações de integridade do dispositivo e retorna a decisão de risco do backend.
O SDK nunca guarda uma chave de API da Surt. Seu backend guarda a chave sp_live_* (somente no lado do servidor) e a troca por um JWT de curta duração que o app passa para verify().
Este guia cobre o SDK v0.3.0, que também corrige a detecção de VPN.
Requisitos
- Android SDK 24+ (Android 7.0+)
- Kotlin 1.9+
- Gradle 8+
- Java 17
1. Autenticação
Adicione seu token de acesso ao gradle.properties do seu projeto (ou ~/.gradle/gradle.properties para todos os projetos):
SURT_GITHUB_TOKEN=<YOUR_TOKEN>
Substitua <YOUR_TOKEN> pelo token fornecido pela Surt.
Não faça commit do gradle.properties com tokens no controle de versão. Adicione-o ao .gitignore, ou use o ~/.gradle/gradle.properties em vez disso.
2. Adicionar o Repositório
No settings.gradle (ou settings.gradle.kts) do seu projeto, adicione o repositório Maven da Surt:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/surtTech/surt-guardian-sdk")
credentials {
username = "surt-customer"
password = settings.ext.find("SURT_GITHUB_TOKEN")
?: System.getenv("SURT_GITHUB_TOKEN") ?: ""
}
}
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/surtTech/surt-guardian-sdk")
credentials {
username = "surt-customer"
password = providers.gradleProperty("SURT_GITHUB_TOKEN").orNull
?: System.getenv("SURT_GITHUB_TOKEN")
}
}
}
}
3. Adicionar a Dependência
No build.gradle (ou build.gradle.kts) do seu app:
dependencies {
implementation 'com.surt.guardian:securitysdk:0.3.0'
}
dependencies {
implementation("com.surt.guardian:securitysdk:0.3.0")
}
Core Library Desugaring
Se o seu app tem como alvo minSdk < 26, garanta que o core library desugaring esteja habilitado no build.gradle do seu app:
android {
compileOptions {
coreLibraryDesugaringEnabled true
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
}
4. Permissões do AndroidManifest
Se você habilitar a coleta de localização, adicione estas permissões ao seu AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Se você habilitar a coleta de informações do cartão SIM, adicione esta permissão:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
O SDK solicita automaticamente a permissão de localização durante a verificação quando collectLocation está habilitado. A caixa de diálogo de permissão padrão do Android aparece automaticamente - nenhum código adicional é necessário.
5. Inicializar o SDK
Chame uma vez na inicialização do app (tipicamente na sua classe Application). Nenhuma chave de API é necessária no SDK - sua chave de API do lado do servidor nunca chega ao dispositivo.
import android.app.Application
import com.surt.guardian.GuardianSDK
import com.surt.guardian.core.Environment
import com.surt.guardian.core.FailurePolicy
import com.surt.guardian.core.GuardianOptions
import com.surt.guardian.utils.Logger
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
GuardianSDK.initialize(
context = this,
options = GuardianOptions(
environment = Environment.Production,
failurePolicy = FailurePolicy.Fail,
logLevel = Logger.Level.WARN,
collectLocation = true // Habilita a coleta de GPS (opcional)
)
)
}
}
Ambientes
| Ambiente | URL Base |
|---|---|
Environment.Production | https://api.surt.com |
Environment.Sandbox | https://sandbox-api.surt.com |
Opções de Coleta de Dados
| Opção | Padrão | Descrição |
|---|---|---|
collectLocation | false | Coordenadas GPS - requer permissões de localização no manifest |
collectWifiInfo | false | Detalhes de rede WiFi |
collectSimCardInfo | false | Informações de SIM/operadora - requer a permissão READ_PHONE_STATE |
collectCameraInfo | false | Contagem/informações da câmera |
Quando collectLocation está habilitado, o SDK solicita automaticamente ao usuário a permissão de localização na primeira vez que verify() é chamado. Se o usuário negar, o SDK continua sem dados de GPS - o backend pontua a transação com menos confiança.
6. Referência da Activity (para solicitações automáticas de permissão)
Para que o SDK exiba a caixa de diálogo de permissão, ele precisa de uma referência à Activity atual. Chame setActivity() no ciclo de vida da sua Activity:
override fun onResume() {
super.onResume()
GuardianSDK.getInstance().setActivity(this)
}
override fun onPause() {
super.onPause()
GuardianSDK.getInstance().setActivity(null)
}
Se você não chamar setActivity(), o SDK ainda funciona, mas não pode solicitar permissões automaticamente. Você precisaria solicitar as permissões por conta própria antes de chamar verify().
7. Gerar um JWT a Partir do Seu Backend
Antes de cada chamada verify(), seu app deve obter um GeolocationJwt de curta duração do seu próprio backend. Seu backend chama POST /geolocation/preflight com sua chave de API do lado do servidor e o contexto da transação, e então retorna o JWT para o app.
Exemplo de chamada do backend (seu servidor, não o app):
POST /geolocation/preflight
Authorization: Bearer sp_live_xxx
Content-Type: application/json
{
"customer_id": "user_abc123",
"transaction_type": "login",
"transaction_name": "User Login",
"name": "John Doe",
"email": "john@example.com"
}
A resposta do preflight retorna o token que seu app precisa:
{
"data": {
"token": "<jwt>"
}
}
Seu app obtém o JWT a partir do endpoint do seu próprio backend:
suspend fun fetchVerifyJwt(): String {
// Chame o endpoint do SEU PRÓPRIO backend, não a Surt diretamente
val response = httpClient.post("https://your-api.com/geolocation-jwt")
return response.body<YourJwtResponse>().jwt
}
Sempre obtenha um JWT novo imediatamente antes de chamar verify(). Cada JWT é de uso único; reutilizar um JWT é rejeitado pelo backend.
8. Verificar uma Transação
Chame em momentos sensíveis à segurança (login, pagamento, etc.). Obtenha um JWT do seu backend imediatamente antes de cada chamada.
val jwt = fetchVerifyJwt()
val result = GuardianSDK.getInstance().verifySuspend(jwt = jwt)
if (result.allowed) {
// Prosseguir com a transação
} else {
// Tratar transação negada (result.riskLevel tem os detalhes)
}
Ou com um callback:
val jwt = fetchVerifyJwt()
GuardianSDK.getInstance().verify(jwt = jwt) { result ->
result.onSuccess { verification ->
if (verification.allowed) { /* prosseguir */ }
}
result.onFailure { error ->
// Tratar SurtError
}
}
Substituição de localização por chamada:
// Forçar GPS ligado para esta chamada, independentemente do padrão de inicialização
val result = GuardianSDK.getInstance().verifySuspend(
jwt = jwt,
collectLocation = true
)
9. Collect (Servidor para Servidor, Opcional)
collect() retorna um payload criptografado para o seu backend encaminhar à Surt. Nenhuma chamada direta do SDK para a Surt acontece durante o collect.
// Sem JWT - nenhuma chamada de rede para a Surt, sem IP no payload
val result = GuardianSDK.getInstance().collectSuspend()
// Com JWT - resolve o IP público do dispositivo (GET /geolocation/client-ip),
// e o embute no payload (melhor esforço)
val jwtForCollect = fetchCollectJwt()
val result = GuardianSDK.getInstance().collectSuspend(jwt = jwtForCollect)
// Envie result.payload para o seu backend
O JWT para collect() é opcional. Passe-o apenas para embutir o IP público do dispositivo. Diferente de verify(), um JWT para collect() pode ser reutilizado ou omitido - collect() não exige um JWT por chamada e não faz nenhuma chamada de rede à Surt quando nenhum JWT é fornecido.
Resultado da Verificação
data class VerificationResult(
val allowed: Boolean, // Decisão do backend - true = prosseguir
val riskLevel: RiskLevel, // LOW / MEDIUM / HIGH / BLOCKED / UNKNOWN
val sessionId: String, // ID da transação para referência de suporte
val errors: List<String>?, // Mensagens de erro do backend, se houver
val timestamp: Long // Timestamp da resposta (ms)
)
Tipos de Transação
Estes valores são definidos pelo seu backend no campo transaction_type do preflight. Eles não são passados para verify() no app.
| Tipo | Caso de uso |
|---|---|
TransactionType.LOGIN | Login do usuário |
TransactionType.SIGN_UP | Criação de nova conta |
TransactionType.DEPOSIT | Adição de fundos |
TransactionType.WITHDRAWAL | Saque de fundos |
Solução de Problemas
| Erro | Causa | Solução |
|---|---|---|
| "Could not resolve com.surt.guardian:securitysdk" | Token não configurado ou repositório não adicionado | Verifique o gradle.properties e o settings.gradle |
| "401 Unauthorized" de maven.pkg.github.com | Token expirado ou inválido | Obtenha um novo token da Surt |
.notInitialized | verify() chamado antes de initialize() | Chame initialize() em Application.onCreate() |
.invalidJwt | JWT ausente, malformado ou rejeitado | Garanta que seu backend gere um JWT novo a cada chamada |
.attestationFailed | Falha na verificação de integridade do dispositivo - geralmente um JWT reutilizado | Nunca reutilize um JWT entre chamadas verify() |