
在开发需要账号密码登录的Android应用时,我们通常需要实现以下安全策略:
"连续输错密码5次后锁定账号10分钟,期间提示用户稍后重试。锁定状态在成功登录或设备重启后自动解除。"
早期实现可能直接依赖 System.currentTimeMillis() 记录锁定时间,但这种方式存在严重问题:
SystemClock.elapsedRealtime()(从系统启动开始累计的毫秒数,不受用户修改时间影响) SharedPreferences 存储错误次数和锁定截止时间 // 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分钟(毫秒)
}在 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) {
prefs.edit {
putInt(KEY_ERROR_COUNT, 0)
putLong(KEY_LOCK_UNTIL, 0L)
}
}
// 更新设备运行时间记录
prefs.edit { putLong(KEY_LAST_ELAPSED, currentElapsed) }
}
}class LoginActivity : AppCompatActivity() {
// ...
fun attemptLogin(username: String, password: String) {
val (errorCount, lockUntil) = prefs.run {
getInt(KEY_ERROR_COUNT, 0) to getLong(KEY_LOCK_UNTIL, 0L)
}
val currentElapsed = SystemClock.elapsedRealtime()
// 检查锁定状态
if (lockUntil > currentElapsed) {
showLockMessage(lockUntil - currentElapsed)
return
}
when {
isValidCredentials(username, password) -> handleLoginSuccess()
else -> handleLoginError(errorCount, currentElapsed)
}
}
private fun showLockMessage(remainingMs: Long) {
val minutes = remainingMs / 60000
Toast.makeText(this, "请等待 ${minutes} 分钟后再试", LENGTH_SHORT).show()
}
private fun handleLoginSuccess() {
prefs.edit {
putInt(KEY_ERROR_COUNT, 0)
putLong(KEY_LOCK_UNTIL, 0L)
}
startActivity(Intent(this, MainActivity::class.java))
}
private fun handleLoginError(errorCount: Int, currentElapsed: Long) {
val newCount = errorCount + 1
prefs.edit {
if (newCount >= MAX_ATTEMPTS) {
putLong(KEY_LOCK_UNTIL, currentElapsed + LOCK_DURATION_MS)
Toast.makeText(this, "错误次数过多,请10分钟后重试", LENGTH_SHORT).show()
} else {
Toast.makeText(this, "密码错误,剩余尝试次数: ${MAX_ATTEMPTS - newCount}", LENGTH_SHORT).show()
}
putInt(KEY_ERROR_COUNT, newCount)
}
}
}方法 | 特点 | 是否受用户修改时间影响 |
|---|---|---|
| 系统当前时间 | ✅ 受影响 |
| 设备启动后运行时间 | ❌ 不受影响 |
val current = SystemClock.elapsedRealtime() // 当前设备运行时间
val last = prefs.getLong(KEY_LAST_ELAPSED, 0)
if (current < last) {
// 设备已重启,因为运行时间不可能倒流
resetLockState()
}stateDiagram-v2
[*] --> Idle: 初始状态
Idle --> Locked: 连续错误5次
Locked --> Idle: 10分钟到期或设备重启
Locked --> Idle: 成功登录EncryptedSharedPreferences 加密存储敏感数据 本文实现的登录锁定机制具有以下优势:
SharedPreferences 最终代码已验证兼容性:
你有更好的实现思路吗?欢迎在评论区讨论!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。