Android
Integración nativa de Android para el SDK de Surt Guardian, distribuido mediante Maven (GitHub Packages). El SDK recopila señales del dispositivo, ejecuta verificaciones de integridad del dispositivo y devuelve la decisión de riesgo del backend.
El SDK nunca maneja una clave de API de Surt. Tu backend maneja la clave sp_live_* (solo del lado del servidor) y la intercambia por un JWT de corta duración que la app pasa a verify().
Esta guía cubre el SDK v0.3.0, que también corrige la detección de VPN.
Requisitos
- Android SDK 24+ (Android 7.0+)
- Kotlin 1.9+
- Gradle 8+
- Java 17
1. Autenticación
Agrega tu token de acceso al gradle.properties de tu proyecto (o a ~/.gradle/gradle.properties para todos los proyectos):
SURT_GITHUB_TOKEN=<YOUR_TOKEN>
Reemplaza <YOUR_TOKEN> con el token proporcionado por Surt.
No confirmes gradle.properties con tokens en el control de versiones. Agrégalo a .gitignore, o usa ~/.gradle/gradle.properties en su lugar.
2. Agregar el repositorio
En el settings.gradle (o settings.gradle.kts) de tu proyecto, agrega el repositorio Maven de 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. Agregar la dependencia
En el build.gradle (o build.gradle.kts) de tu app:
dependencies {
implementation 'com.surt.guardian:securitysdk:0.3.0'
}
dependencies {
implementation("com.surt.guardian:securitysdk:0.3.0")
}
Core Library Desugaring
Si tu app apunta a minSdk < 26, asegúrate de que el core library desugaring esté habilitado en el build.gradle de tu app:
android {
compileOptions {
coreLibraryDesugaringEnabled true
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
}
4. Permisos de AndroidManifest
Si habilitas la recopilación de ubicación, agrega estos permisos a tu AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Si habilitas la recopilación de información de tarjeta SIM, agrega este permiso:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
El SDK solicita automáticamente el permiso de ubicación durante la verificación cuando collectLocation está habilitado. El cuadro de diálogo de permisos estándar de Android aparece automáticamente; no se necesita código adicional.
5. Inicializar el SDK
Llama una sola vez al arrancar la app (normalmente en tu clase Application). No se requiere ninguna clave de API en el SDK; tu clave de API del lado del servidor nunca se entrega al 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 // Enable GPS collection (optional)
)
)
}
}
Entornos
| Entorno | URL base |
|---|---|
Environment.Production | https://api.surt.com |
Environment.Sandbox | https://sandbox-api.surt.com |
Opciones de recopilación de datos
| Opción | Predeterminado | Descripción |
|---|---|---|
collectLocation | false | Coordenadas GPS - requiere permisos de ubicación en el manifest |
collectWifiInfo | false | Detalles de red WiFi |
collectSimCardInfo | false | Información de SIM/operador - requiere el permiso READ_PHONE_STATE |
collectCameraInfo | false | Cantidad/información de cámara |
Cuando collectLocation está habilitado, el SDK solicita automáticamente al usuario el permiso de ubicación la primera vez que se llama a verify(). Si el usuario lo deniega, el SDK continúa sin datos GPS; el backend evalúa la transacción con menos confianza.
6. Referencia de Activity (para solicitudes de permisos automáticas)
Para que el SDK muestre el cuadro de diálogo de permisos, necesita una referencia a la Activity actual. Llama a setActivity() en el ciclo de vida de tu Activity:
override fun onResume() {
super.onResume()
GuardianSDK.getInstance().setActivity(this)
}
override fun onPause() {
super.onPause()
GuardianSDK.getInstance().setActivity(null)
}
Si no llamas a setActivity(), el SDK sigue funcionando pero no puede solicitar permisos automáticamente. Tendrías que solicitar los permisos tú mismo antes de llamar a verify().
7. Generar un JWT desde tu backend
Antes de cada llamada a verify(), tu app debe obtener un GeolocationJwt de corta duración desde tu propio backend. Tu backend llama a POST /geolocation/preflight con su clave de API del lado del servidor y el contexto de la transacción, y luego devuelve el JWT a la app.
Ejemplo de llamada del backend (tu servidor, no la 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"
}
La respuesta del preflight devuelve el token que tu app necesita:
{
"data": {
"token": "<jwt>"
}
}
Tu app obtiene el JWT desde tu propio endpoint del backend:
suspend fun fetchVerifyJwt(): String {
// Call your OWN backend endpoint, not Surt directly
val response = httpClient.post("https://your-api.com/geolocation-jwt")
return response.body<YourJwtResponse>().jwt
}
Obtén siempre un JWT fresco justo antes de llamar a verify(). Cada JWT es de un solo uso; reutilizar uno es rechazado por el backend.
8. Verificar una transacción
Llama en momentos sensibles de seguridad (inicio de sesión, pago, etc.). Obtén un JWT de tu backend justo antes de cada llamada.
val jwt = fetchVerifyJwt()
val result = GuardianSDK.getInstance().verifySuspend(jwt = jwt)
if (result.allowed) {
// Proceed with the transaction
} else {
// Handle denied transaction (result.riskLevel has details)
}
O con un callback:
val jwt = fetchVerifyJwt()
GuardianSDK.getInstance().verify(jwt = jwt) { result ->
result.onSuccess { verification ->
if (verification.allowed) { /* proceed */ }
}
result.onFailure { error ->
// Handle SurtError
}
}
Anulación de ubicación por llamada:
// Force GPS on for this call, regardless of init default
val result = GuardianSDK.getInstance().verifySuspend(
jwt = jwt,
collectLocation = true
)
9. Collect (Servidor a servidor, opcional)
collect() devuelve un payload cifrado para que tu backend lo reenvíe a Surt. No ocurren llamadas directas del SDK a Surt durante collect.
// Without JWT - no Surt network calls, no IP in payload
val result = GuardianSDK.getInstance().collectSuspend()
// With JWT - resolves device public IP (GET /geolocation/client-ip),
// embeds it in payload (best-effort)
val jwtForCollect = fetchCollectJwt()
val result = GuardianSDK.getInstance().collectSuspend(jwt = jwtForCollect)
// Send result.payload to your backend
El JWT para collect() es opcional. Pásalo solo para incrustar la IP pública del dispositivo. A diferencia de verify(), un JWT para collect() puede reutilizarse u omitirse: collect() genera su propio nonce internamente y no realiza ninguna llamada de red a Surt cuando no se proporciona un JWT.
Resultado de la verificación
data class VerificationResult(
val allowed: Boolean, // Backend decision - true = proceed
val riskLevel: RiskLevel, // LOW / MEDIUM / HIGH / BLOCKED / UNKNOWN
val sessionId: String, // Transaction ID for support reference
val errors: List<String>?, // Backend error messages, if any
val timestamp: Long // Response timestamp (ms)
)
Tipos de transacción
Estos valores los establece tu backend en el campo transaction_type del preflight. No se pasan a verify() en la app.
| Tipo | Caso de uso |
|---|---|
TransactionType.LOGIN | Inicio de sesión del usuario |
TransactionType.SIGN_UP | Creación de cuenta nueva |
TransactionType.DEPOSIT | Agregar fondos |
TransactionType.WITHDRAWAL | Retirar fondos |
Solución de problemas
| Error | Causa | Solución |
|---|---|---|
| "Could not resolve com.surt.guardian:securitysdk" | Token no configurado o repositorio no agregado | Verifica gradle.properties y settings.gradle |
| "401 Unauthorized" desde maven.pkg.github.com | Token expirado o inválido | Obtén un nuevo token de Surt |
.notInitialized | verify() llamado antes de initialize() | Llama a initialize() en Application.onCreate() |
.invalidJwt | JWT faltante, malformado o rechazado | Asegúrate de que tu backend genere un JWT fresco por cada llamada |
.attestationFailed | Falló una verificación de integridad del dispositivo, normalmente un JWT reutilizado | Nunca reutilices un JWT entre llamadas a verify() |