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
相关产品推荐
相关产品推荐

