在 Web 应用中,通常会需要对 IP 访问进行限制以及控制提交次数,以防止恶意攻击(例如暴力破解、DoS攻击、API滥用等)。为了实现这一功能,我们可以结合 Golang 的特性,使用中间件或者基于 Redis 这样的缓存服务来实现 IP 限制和提交次数的控制。
实现步骤
IP 访问限制:对每个 IP 的访问频次进行限制,比如每个 IP 每分钟只能访问某个接口 10 次。超过限制后,返回错误信息(例如 429 Too Many Requests)。
提交次数限制:通过限制某个时间段内某个 IP 的提交次数,防止暴力破解或者滥用接口。
Redis(或其他存储系统)作为计数器:为了更好地实现这种限制,可以使用 Redis 等缓存系统来存储 IP 的访问记录、提交次数等,因为 Redis 的性能和易用性使它成为理想的选择。
核心概念
Rate Limiting(限流):根据 IP 限制某个时间段内的访问次数。
请求次数计数:对每个 IP 进行计数,并基于计数来判断是否超过限制。
时间窗口:设置一定的时间窗口(例如一分钟或五分钟),在这个时间段内统计 IP 的访问次数。
使用 Golang 及 Redis 实现 IP 访问限制和提交次数限制
这里我们使用 Redis 来存储和控制访问次数,并结合 Go 实现一个简单的 IP 访问限制中间件。
依赖库
你可以使用 Redis 官方的 Go 客户端 go-redis 来连接 Redis 进行操作。先安装这个库:
go get github.com/go-redis/redis/v8
实现代码
下面的代码演示了如何使用 Redis 来实现 IP 访问限制和提交次数限制。
package main import ( "context" "fmt" "log" "net/http" "strconv" "time" "github.com/go-redis/redis/v8" ) // Redis client var rdb *redis.Client // 初始化 Redis 客户端 func initRedis() { rdb = redis.NewClient(&redis.Options{ Addr: "localhost:6379", // Redis 地址 Password: "", // Redis 密码(如果有) DB: 0, // 使用的 Redis 数据库 }) } // 获取客户端的 IP 地址 func getIP(r *http.Request) string { // 尝试从 X-Forwarded-For 或 X-Real-IP 获取真实 IP ip := r.Header.Get("X-Forwarded-For") if ip == "" { ip = r.Header.Get("X-Real-IP") } if ip == "" { ip = r.RemoteAddr } return ip } // 中间件:IP 访问限制 func rateLimitMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := context.Background() ip := getIP(r) key := "rate_limit:" + ip // 获取 Redis 中的访问次数 count, err := rdb.Get(ctx, key).Result() if err == redis.Nil { // 如果没有记录,设置计数为1,并设置过期时间 err := rdb.Set(ctx, key, 1, time.Minute).Err() // 1 分钟限制 if err != nil { http.Error(w, "Redis error", http.StatusInternalServerError) return } } else if err != nil { http.Error(w, "Redis error", http.StatusInternalServerError) return } else { // 将访问次数转换为整数 countInt, _ := strconv.Atoi(count) if countInt >= 10 { // 假设限制为每分钟最多10次 http.Error(w, "Too Many Requests", http.StatusTooManyRequests) return } // 递增计数 rdb.Incr(ctx, key) } next.ServeHTTP(w, r) } } // 示例处理器:提交处理 func submitHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Request successful") } func main() { // 初始化 Redis initRedis() // 创建 HTTP 服务器并添加中间件 http.HandleFunc("/submit", rateLimitMiddleware(submitHandler)) log.Println("Server is running on port 8080...") http.ListenAndServe(":8080", nil) }
代码解析
Redis 客户端初始化:
使用
redis.NewClient()
初始化 Redis 客户端。通过
rdb.Set()
和rdb.Get()
来操作 Redis 中的计数器。IP 获取:
通过
getIP()
函数获取请求的客户端 IP 地址。该函数尝试从请求头中的X-Forwarded-For
或X-Real-IP
获取真实的 IP。如果没有,则使用RemoteAddr
。Rate Limiting 中间件:
rateLimitMiddleware()
是核心的中间件函数,负责限制每个 IP 的访问次数。它使用 Redis 来存储每个 IP 的访问计数和限流时间窗口(这里设置为 1 分钟)。当 IP 的访问次数超过限制时,返回 HTTP 状态码
429 Too Many Requests
。处理请求:
submitHandler()
是一个简单的示例处理器,处理成功的请求。访问
/submit
时,经过中间件限制后,正常情况下返回 "Request successful"。
改进与扩展
动态调整限流策略: 可以根据不同的用户类型、不同的 API 路径动态调整限流策略。例如,VIP 用户可能会有更高的访问频次。
IP 黑名单: 通过 Redis 或其他存储系统维护一个黑名单,遇到黑名单中的 IP 可以直接拒绝请求。
按时间窗口的限流算法: 你可以采用滑动窗口、漏桶算法、令牌桶算法等更复杂的限流算法来实现更灵活的控制。
使用 Redis Expire 特性: 在 Redis 中使用
SetEX
(带过期时间的键设置)或TTL
来确保计数器可以自动重置,避免手动管理。日志记录与报警: 可以结合日志系统,在某个 IP 频繁触发限制时记录日志或发送报警信息。
通过 Golang 和 Redis 的结合,可以轻松实现 IP 访问限制和提交次数控制。Redis 的高性能特性使其非常适合用作限流计数器的存储。在实际应用中,可以根据需要扩展该方案,例如使用不同的限流算法、结合 IP 黑名单等。