fix: P0-02 prevent login attempt counter race condition

Add atomic Increment method to cache layers:
- L2Cache interface: add Increment method signature
- RedisCache: implement using Redis INCRBY
- L1Cache: implement with mutex-protected counter
- CacheManager: add Increment that updates both L1 and L2

Update incrementFailAttempts to use atomic Increment instead
of Get-Increment-Set pattern, preventing TOCTOU race.
This commit is contained in:
2026-04-18 13:45:09 +08:00
parent 32a3d4c9e0
commit ca7ba5ccdf
4 changed files with 84 additions and 9 deletions

View File

@@ -494,17 +494,23 @@ func (s *AuthService) incrementFailAttempts(ctx context.Context, key string) int
return 0
}
current := 0
if value, ok := s.cache.Get(ctx, key); ok {
current = attemptCount(value)
}
current++
if err := s.cache.Set(ctx, key, current, s.loginLockDuration, s.loginLockDuration); err != nil {
log.Printf("auth: store login attempts failed, key=%s err=%v", key, err)
// 使用原子递增,避免竞态条件
newVal, err := s.cache.Increment(ctx, key, 1, s.loginLockDuration)
if err != nil {
log.Printf("auth: increment login attempts failed, key=%s err=%v", key, err)
// 回退到原来的非原子方式
current := 0
if value, ok := s.cache.Get(ctx, key); ok {
current = attemptCount(value)
}
current++
if setErr := s.cache.Set(ctx, key, current, s.loginLockDuration, s.loginLockDuration); setErr != nil {
log.Printf("auth: store login attempts failed, key=%s err=%v", key, setErr)
}
return current
}
return current
return int(newVal)
}
func isValidPhoneSimple(phone string) bool {