You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android信贷评分应用中Token登录认证及过期自动登出方案咨询

Android端Token存储与过期自动登出实现方案

一、Token安全存储方案

Android端存储Token必须优先保障安全性,绝对禁止明文存储,推荐以下两种落地方案:

1. EncryptedSharedPreferences(优先推荐)

借助Jetpack Security库提供的EncryptedSharedPreferences,对Token进行加密存储,避免明文泄露风险。

  • 依赖配置:
implementation "androidx.security:security-crypto:1.1.0-alpha06"
  • 实现代码:
// 创建加密SharedPreferences实例
fun getEncryptedSharedPreferences(context: Context): SharedPreferences {
    val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
    return EncryptedSharedPreferences.create(
        "secure_token_store",
        masterKeyAlias,
        context,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
}

// 存储Token
fun saveToken(context: Context, token: String) {
    getEncryptedSharedPreferences(context).edit()
        .putString("auth_token", token)
        .apply()
}

// 获取Token
fun getToken(context: Context): String? {
    return getEncryptedSharedPreferences(context).getString("auth_token", null)
}

2. Android Keystore

将Token加密后存储,密钥由系统Keystore托管,完全避免密钥泄露可能,适合对安全性要求极高的场景。但实现复杂度较高,若EncryptedSharedPreferences能满足需求,优先选择前者。

二、Token过期检测与自动登出实现

后端返回的JWT Token包含exp字段(秒级过期时间戳),我们可以解析该字段,结合以下方式实现过期检测与自动登出:

1. 解析Token中的过期时间

使用JJWT库简化JWT解析操作,快速提取exp值:

  • 依赖配置:
implementation "io.jsonwebtoken:jjwt-api:0.11.5"
runtimeOnly "io.jsonwebtoken:jjwt-impl:0.11.5"
runtimeOnly "io.jsonwebtoken:jjwt-jackson:0.11.5"
  • 解析代码:
fun getTokenExpirationTime(token: String): Long? {
    return try {
        Jwts.parserBuilder()
            // 若后端要求验证签名,需添加setSigningKey传入签名密钥;仅解析Payload可跳过此步骤
            .build()
            .parseClaimsJws(token)
            .body
            .expiration
            ?.time
    } catch (e: Exception) {
        // 解析失败,判定Token无效
        null
    }
}

// 存储Token时同步保存过期时间
fun saveTokenWithExpiry(context: Context, token: String) {
    val expiryTime = getTokenExpirationTime(token)
    getEncryptedSharedPreferences(context).edit()
        .putString("auth_token", token)
        .putLong("token_expiry", expiryTime ?: 0)
        .apply()
}

2. 过期检测的三种落地方式

(1)网络请求前统一拦截检测

在OkHttp拦截器中处理所有请求,每次请求前校验Token有效性:

class AuthInterceptor(private val context: Context) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val token = getToken(context)
        val expiryTime = getEncryptedSharedPreferences(context).getLong("token_expiry", 0)
        
        if (token.isNullOrEmpty() || System.currentTimeMillis() > expiryTime) {
            // Token过期或不存在,触发登出逻辑
            logout(context)
            throw IOException("Token expired")
        }
        
        // 为请求添加Token头
        val request = chain.request().newBuilder()
            .addHeader("Authorization", "Bearer $token")
            .build()
        return chain.proceed(request)
    }
    
    private fun logout(context: Context) {
        // 清除Token及过期时间
        getEncryptedSharedPreferences(context).edit()
            .remove("auth_token")
            .remove("token_expiry")
            .apply()
        // 跳转到登录页并清空Activity栈
        val intent = Intent(context, LoginActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        context.startActivity(intent)
    }
}

(2)后台定时静默检测

使用WorkManager实现周期性任务,定时检查Token是否过期,适合需要后台自动登出的场景:

class TokenExpiryWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        val expiryTime = getEncryptedSharedPreferences(applicationContext).getLong("token_expiry", 0)
        if (System.currentTimeMillis() > expiryTime) {
            logout(applicationContext)
        }
        return Result.success()
    }
    
    private fun logout(context: Context) {
        // 同上述登出逻辑
        getEncryptedSharedPreferences(context).edit()
            .remove("auth_token")
            .remove("token_expiry")
            .apply()
        val intent = Intent(context, LoginActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        context.startActivity(intent)
    }
}

// 启动定时检测任务(每小时检查一次)
fun startTokenExpiryCheck(context: Context) {
    val request = PeriodicWorkRequestBuilder<TokenExpiryWorker>(1, TimeUnit.HOURS)
        .build()
    WorkManager.getInstance(context).enqueue(request)
}

(3)页面启动时检测

在BaseActivity的onResume方法中添加校验逻辑,确保用户进入页面时Token有效:

open class BaseActivity : AppCompatActivity() {
    override fun onResume() {
        super.onResume()
        val expiryTime = getEncryptedSharedPreferences(this).getLong("token_expiry", 0)
        if (System.currentTimeMillis() > expiryTime) {
            logout(this)
        }
    }
    
    private fun logout(context: Context) {
        // 同上述登出逻辑
        getEncryptedSharedPreferences(context).edit()
            .remove("auth_token")
            .remove("token_expiry")
            .apply()
        val intent = Intent(context, LoginActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        startActivity(intent)
        finish()
    }
}

三、额外注意事项

  • 若后端支持Token刷新机制,可在检测到Token即将过期时自动调用刷新接口获取新Token,减少用户登录频率。
  • 登出时必须清除所有用户敏感数据,避免残留泄露风险。
  • JWT解析时,若后端开启签名验证,必须传入正确的签名密钥,否则会解析失败。

内容的提问来源于stack exchange,提问作者treeoid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 19:48:17