📖 模块简介
在金融等高安全性场景下,登录接口极易成为暴力破解攻击的目标。通过 Redis 的高性能计数器和 Lua 脚本原子性,可以实现每分钟登录尝试次数限制,及时阻断异常行为,提升系统安全性。本文系统介绍其原理、应用场景、Go+Redis+Lua 实现、常见问题与最佳实践。
🧠 基础原理
- 计数器限流:每个用户(或 IP)登录尝试时,使用 Redis 的 INCR/EXPIRE 或 Lua 脚本对尝试次数进行原子性累加。
- 时间窗口:以分钟为单位设置计数器过期时间,自动滑动时间窗口。
- Lua 脚本原子操作:用 Lua 保证加计数和设置过期的原子性,防止并发下计数器失效。
- 超限阻断:当计数器超过阈值(如 5 次/分钟)即拒绝登录,返回友好提示。
💼 金融业务应用场景
- 账户登录防暴力破解:限制每个账户/手机号/设备每分钟登录尝试次数,防止被撞库。
- 支付/敏感操作防刷:对敏感操作接口(如支付、提现)做频率限制。
- API 接口防刷限流:对外开放 API 做细粒度限流,防止恶意攻击。
💻 示例代码(Go + Redis + Lua)
1. Lua 脚本(每分钟限 5 次)
-- KEYS[1]: 计数器 key(如 login:attempt:userid)
-- ARGV[1]: 限制次数(如5)
-- ARGV[2]: 过期时间(秒,60)
local cnt = redis.call('INCR', KEYS[1])
if cnt == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[2])
end
if cnt > tonumber(ARGV[1]) then
return 0 -- 超限
else
return 1 -- 未超限
end
2. Go 端调用示例
script := `
local cnt = redis.call('INCR', KEYS[1])
if cnt == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[2])
end
if cnt > tonumber(ARGV[1]) then
return 0
else
return 1
end`
userID := "u1001"
key := fmt.Sprintf("login:attempt:%s", userID)
limit := 5
expire := 60 // 秒
res, err := rdb.Eval(ctx, script, []string{key}, limit, expire).Result()
if err != nil {
// Redis 异常处理
}
if res.(int64) == 0 {
// 超过限制,拒绝登录
return errors.New("登录过于频繁,请稍后再试")
}
// 继续登录逻辑
🚨 常见问题与注意事项
- 分布式部署一致性:多节点需共用同一 Redis,防止绕过限制。
- Lua 脚本返回类型:注意 Go 端类型断言,避免类型不匹配。
- 恶意用户绕过:建议对用户ID和IP双重限流,提升防护。
- Redis 持久化与高可用:Redis 故障可能导致限流失效,需保障高可用。
- 误伤正常用户:阈值设置需平衡安全与用户体验。
✅ 最佳实践建议
- 用户ID与IP双重限流,提升防护深度。
- 阈值设置结合业务实际,避免误伤。
- Redis 需高可用部署,防止单点故障。
- 结合黑名单、验证码等多重防护措施。
- 定期监控限流命中率,动态调整策略。