一、问题背景
在开发需要账号密码登录的Android应用时,我们通常需要实现以下安全策略:
“连续输错密码5次后锁定账号10分钟,期间提示用户稍后重试。锁定状态在成功登录或设备重启后自动解除。”
传统方案的缺陷
早期实现可能直接依赖 System.currentTimeMillis()
记录锁定时间,但这种方式存在严重问题:
- 用户手动修改系统时间可绕过锁定机制
- 设备重启后无法准确判断是否应解除锁定
二、解决方案设计
核心思路
- 基于设备运行时间
- 使用
SystemClock.elapsedRealtime()
(从系统启动开始累计的毫秒数,不受用户修改时间影响)
- 使用
- 可靠的重启检测
- 比较两次记录的设备运行时间,若当前值小于上次记录值,则判定为设备已重启
- 状态持久化
- 通过
SharedPreferences
存储错误次数和锁定截止时间
- 通过
三、完整 Kotlin 实现
1. 定义常量
// Constants.kt
object LockConfig {
const val PREF_NAME = "account_lock_prefs"
const val KEY_ERROR_COUNT = "error_count"
const val KEY_LOCK_UNTIL = "lock_until" // 锁定截止时间(基于elapsedRealtime)
const val KEY_LAST_ELAPSED = "last_elapsed" // 上次设备运行时间
const val MAX_ATTEMPTS = 5
const val LOCK_DURATION_MS = 10 * 60 * 1000L // 10分钟(毫秒)
}
2. 设备重启检测
在 LoginActivity
的 onCreate
中初始化:
class LoginActivity : AppCompatActivity() {
private val prefs by lazy {
getSharedPreferences(LockConfig.PREF_NAME, MODE_PRIVATE)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
detectSystemReboot()
}
/** 检测设备是否重启并重置状态 */
private fun detectSystemReboot() {
val currentElapsed = SystemClock.elapsedRealtime()
val lastElapsed = prefs.getLong(KEY_LAST_ELAPSED, 0L)
// 当前时间小于上次记录时间 = 设备已重启
if (currentElapsed < lastElapsed