不能只靠前端验证码防暴力破解,因为攻击者可绕过前端直接调用/login接口;必须由服务端中间件在路由处理前基于ip+账号双维度、原子化计数并限流,配合redis实现5分钟窗口内超5次即拒。

为什么不能只靠前端验证码防暴力破解
图形验证码只是第一道防线,它无法阻止攻击者绕过前端、直接调用 /login 接口发起高频请求。Iris 的中间件必须在路由处理前就完成请求频次判定,否则验证码校验逻辑还没走到,账号密码参数就已经被反复提交了。
真正有效的防爆破,得靠服务端对「同一 IP + 同一账号」或「同一 IP」维度的请求计数与时间窗口控制,且必须原子化——避免并发请求下计数错乱。
用 iris.Limiter 实现 IP 级限流(简单但有缺陷)
Iris 内置的 iris.Limiter 可快速启用基于 IP 的速率限制,但它不区分账号,也无法绑定业务逻辑(比如只对 /login 生效、失败才计数):
-
iris.Limiter(10, time.Minute)表示每分钟最多 10 次请求,所有路径共用该计数器 - 若用户正常刷首页也计入,会误伤;若只挂到
/login,又无法识别「同一个账号被不同 IP 轮询爆破」 - 它底层用的是内存计数,重启即清零,不适合生产环境长期防护
自定义中间件:按「IP + 账号」双维度限流
核心是用 Redis 存储带 TTL 的计数键,例如 login:ip:192.168.1.100 和 login:user:admin,并在登录失败时递增、成功时重置。关键点:
- 中间件需在
ctx.Request().URL.Path == "/login"且方法为POST时才介入 - 从请求体解析账号字段(如
username),不能依赖 query 或 header,防止伪造 - 使用
redis.Incr+redis.Expire原子操作,TTL 设为 5 分钟比 1 分钟更抗短时脉冲 - 若任一计数超阈值(如 5 次/5 分钟),直接
ctx.StatusCode(429)并返回{"error": "too many attempts"},不再执行后续 handler
示例伪代码片段:
func LoginRateLimitMiddleware() iris.Handler {
return func(ctx iris.Context) {
if ctx.Method() != "POST" || ctx.Request().URL.Path != "/login" {
ctx.Next()
return
}
ip := ctx.RemoteAddr()
var req struct{ Username string }
if err := ctx.ReadJSON(&req); err != nil {
ctx.StatusCode(400)
return
}
keyIP := fmt.Sprintf("login:ip:%s", ip)
keyUser := fmt.Sprintf("login:user:%s", req.Username)
countIP, _ := redisClient.Incr(ctx, keyIP).Result()
redisClient.Expire(ctx, keyIP, 5*time.Minute)
countUser, _ := redisClient.Incr(ctx, keyUser).Result()
redisClient.Expire(ctx, keyUser, 5*time.Minute)
if countIP > 5 || countUser > 5 {
ctx.StatusCode(429)
ctx.JSON(map[string]string{"error": "too many attempts"})
return
}
ctx.Next()
}
}
容易忽略的三个细节
实际部署时,以下三点最容易导致防护失效:
- 没做
ctx.Request().Body读取后的重放——Iris 的ctx.ReadJSON会消耗 body 流,后续 login handler 再读就为空,需用ctx.Request().Body = io.NopCloser(bytes.NewBuffer(bodyBytes))恢复 - Redis 连接未设置超时或重试,中间件异常 panic 会导致整个请求链路中断,必须包在
defer func(){...}()里捕获 - 没考虑代理场景,
ctx.RemoteAddr()可能始终是 Nginx 内网地址,应优先取X-Forwarded-For头,但需校验是否可信(配合iris.TCPKeepAlive或白名单)
双维度限流不是银弹——它无法防御分布式慢速爆破或撞库,但对常见脚本式暴力破解,已足够形成有效门槛。真正关键的,是把计数逻辑和业务判断解耦,确保中间件只管「能不能进」,不管「怎么登」。











